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 *****************************************************************/
38 /* FUNCTIONS ****************************************************************/
41 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord
)
43 PNTFS_ATTR_CONTEXT Context
;
45 Context
= ExAllocatePoolWithTag(NonPagedPool
,
46 FIELD_OFFSET(NTFS_ATTR_CONTEXT
, Record
) + AttrRecord
->Length
,
48 RtlCopyMemory(&Context
->Record
, AttrRecord
, AttrRecord
->Length
);
49 if (AttrRecord
->IsNonResident
)
51 LONGLONG DataRunOffset
;
52 ULONGLONG DataRunLength
;
54 Context
->CacheRun
= (PUCHAR
)&Context
->Record
+ Context
->Record
.NonResident
.MappingPairsOffset
;
55 Context
->CacheRunOffset
= 0;
56 Context
->CacheRun
= DecodeRun(Context
->CacheRun
, &DataRunOffset
, &DataRunLength
);
57 Context
->CacheRunLength
= DataRunLength
;
58 if (DataRunOffset
!= -1)
61 Context
->CacheRunStartLCN
=
62 Context
->CacheRunLastLCN
= DataRunOffset
;
67 Context
->CacheRunStartLCN
= -1;
68 Context
->CacheRunLastLCN
= 0;
70 Context
->CacheRunCurrentOffset
= 0;
78 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context
)
80 ExFreePoolWithTag(Context
, TAG_NTFS
);
88 * Searches a file record for an attribute matching the given type and name.
91 * Optional pointer to a ULONG that will receive the offset of the found attribute
92 * from the beginning of the record. Can be set to NULL.
95 FindAttribute(PDEVICE_EXTENSION Vcb
,
96 PFILE_RECORD_HEADER MftRecord
,
100 PNTFS_ATTR_CONTEXT
* AttrCtx
,
105 FIND_ATTR_CONTXT Context
;
106 PNTFS_ATTR_RECORD Attribute
;
108 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %u, %p)\n", Vcb
, MftRecord
, Type
, Name
, NameLength
, AttrCtx
);
111 Status
= FindFirstAttribute(&Context
, Vcb
, MftRecord
, FALSE
, &Attribute
);
112 while (NT_SUCCESS(Status
))
114 if (Attribute
->Type
== Type
&& Attribute
->NameLength
== NameLength
)
120 AttrName
= (PWCHAR
)((PCHAR
)Attribute
+ Attribute
->NameOffset
);
121 DPRINT("%.*S, %.*S\n", Attribute
->NameLength
, AttrName
, NameLength
, Name
);
122 if (RtlCompareMemory(AttrName
, Name
, NameLength
<< 1) == (NameLength
<< 1))
134 /* Found it, fill up the context and return. */
135 DPRINT("Found context\n");
136 *AttrCtx
= PrepareAttributeContext(Attribute
);
138 (*AttrCtx
)->FileMFTIndex
= MftRecord
->MFTRecordNumber
;
141 *Offset
= Context
.Offset
;
143 FindCloseAttribute(&Context
);
144 return STATUS_SUCCESS
;
148 Status
= FindNextAttribute(&Context
, &Attribute
);
151 FindCloseAttribute(&Context
);
152 return STATUS_OBJECT_NAME_NOT_FOUND
;
157 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord
)
159 if (AttrRecord
->IsNonResident
)
160 return AttrRecord
->NonResident
.AllocatedSize
;
162 return AttrRecord
->Resident
.ValueLength
;
167 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord
)
169 if (AttrRecord
->IsNonResident
)
170 return AttrRecord
->NonResident
.DataSize
;
172 return AttrRecord
->Resident
.ValueLength
;
176 InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext
,
177 PFILE_RECORD_HEADER FileRecord
,
181 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
182 ULONG NextAttributeOffset
;
184 DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext
, FileRecord
, AttrOffset
, DataSize
);
186 // update ValueLength Field
187 AttrContext
->Record
.Resident
.ValueLength
=
188 Destination
->Resident
.ValueLength
= DataSize
;
190 // calculate the record length and end marker offset
191 AttrContext
->Record
.Length
=
192 Destination
->Length
= DataSize
+ AttrContext
->Record
.Resident
.ValueOffset
;
193 NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
195 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
196 if (NextAttributeOffset
% 8 != 0)
198 USHORT Padding
= 8 - (NextAttributeOffset
% 8);
199 NextAttributeOffset
+= Padding
;
200 AttrContext
->Record
.Length
+= Padding
;
201 Destination
->Length
+= Padding
;
204 // advance Destination to the final "attribute" and write the end type
205 Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Destination
+ Destination
->Length
);
206 Destination
->Type
= AttributeEnd
;
208 // write the final marker (which shares the same offset and type as the Length field)
209 Destination
->Length
= FILE_RECORD_END
;
211 FileRecord
->BytesInUse
= NextAttributeOffset
+ (sizeof(ULONG
) * 2);
215 * @parameter FileRecord
216 * Pointer to a file record. Must be a full record at least
217 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
220 SetAttributeDataLength(PFILE_OBJECT FileObject
,
222 PNTFS_ATTR_CONTEXT AttrContext
,
224 PFILE_RECORD_HEADER FileRecord
,
225 PLARGE_INTEGER DataSize
)
227 NTSTATUS Status
= STATUS_SUCCESS
;
228 ULONG BytesPerCluster
= Fcb
->Vcb
->NtfsInfo
.BytesPerCluster
;
230 // are we truncating the file?
231 if (DataSize
->QuadPart
< AttributeDataLength(&AttrContext
->Record
))
233 if (!MmCanFileBeTruncated(FileObject
->SectionObjectPointer
, DataSize
))
235 DPRINT1("Can't truncate a memory-mapped file!\n");
236 return STATUS_USER_MAPPED_FILE
;
240 if (AttrContext
->Record
.IsNonResident
)
242 ULONGLONG AllocationSize
= ROUND_UP(DataSize
->QuadPart
, BytesPerCluster
);
243 PNTFS_ATTR_RECORD DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
244 ULONG ExistingClusters
= AttrContext
->Record
.NonResident
.AllocatedSize
/ BytesPerCluster
;
246 // do we need to increase the allocation size?
247 if (AttrContext
->Record
.NonResident
.AllocatedSize
< AllocationSize
)
249 ULONG ClustersNeeded
= (AllocationSize
/ BytesPerCluster
) - ExistingClusters
;
250 LARGE_INTEGER LastClusterInDataRun
;
251 ULONG NextAssignedCluster
;
252 ULONG AssignedClusters
;
254 NTSTATUS Status
= GetLastClusterInDataRun(Fcb
->Vcb
, &AttrContext
->Record
, (PULONGLONG
)&LastClusterInDataRun
.QuadPart
);
256 DPRINT1("GetLastClusterInDataRun returned: %I64u\n", LastClusterInDataRun
.QuadPart
);
257 DPRINT1("Highest VCN of record: %I64u\n", AttrContext
->Record
.NonResident
.HighestVCN
);
259 while (ClustersNeeded
> 0)
261 Status
= NtfsAllocateClusters(Fcb
->Vcb
,
262 LastClusterInDataRun
.LowPart
+ 1,
264 &NextAssignedCluster
,
267 if (!NT_SUCCESS(Status
))
269 DPRINT1("Error: Unable to allocate requested clusters!\n");
273 // now we need to add the clusters we allocated to the data run
274 Status
= AddRun(Fcb
->Vcb
, AttrContext
, AttrOffset
, FileRecord
, NextAssignedCluster
, AssignedClusters
);
275 if (!NT_SUCCESS(Status
))
277 DPRINT1("Error: Unable to add data run!\n");
281 ClustersNeeded
-= AssignedClusters
;
282 LastClusterInDataRun
.LowPart
= NextAssignedCluster
+ AssignedClusters
- 1;
285 else if (AttrContext
->Record
.NonResident
.AllocatedSize
> AllocationSize
)
287 // shrink allocation size
288 ULONG ClustersToFree
= ExistingClusters
- (AllocationSize
/ BytesPerCluster
);
289 Status
= FreeClusters(Fcb
->Vcb
, AttrContext
, AttrOffset
, FileRecord
, ClustersToFree
);
292 // TODO: is the file compressed, encrypted, or sparse?
294 // NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource
296 Fcb
->RFCB
.AllocationSize
.QuadPart
= AllocationSize
;
297 AttrContext
->Record
.NonResident
.AllocatedSize
= AllocationSize
;
298 AttrContext
->Record
.NonResident
.DataSize
= DataSize
->QuadPart
;
299 AttrContext
->Record
.NonResident
.InitializedSize
= DataSize
->QuadPart
;
301 DestinationAttribute
->NonResident
.AllocatedSize
= AllocationSize
;
302 DestinationAttribute
->NonResident
.DataSize
= DataSize
->QuadPart
;
303 DestinationAttribute
->NonResident
.InitializedSize
= DataSize
->QuadPart
;
305 DPRINT("Allocated Size: %I64u\n", DestinationAttribute
->NonResident
.AllocatedSize
);
309 // resident attribute
311 // find the next attribute
312 ULONG NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
313 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((PCHAR
)FileRecord
+ NextAttributeOffset
);
315 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
317 // Do we need to increase the data length?
318 if (DataSize
->QuadPart
> AttrContext
->Record
.Resident
.ValueLength
)
320 // There's usually padding at the end of a record. Do we need to extend past it?
321 ULONG MaxValueLength
= AttrContext
->Record
.Length
- AttrContext
->Record
.Resident
.ValueOffset
;
322 if (MaxValueLength
< DataSize
->LowPart
)
324 // If this is the last attribute, we could move the end marker to the very end of the file record
325 MaxValueLength
+= Fcb
->Vcb
->NtfsInfo
.BytesPerFileRecord
- NextAttributeOffset
- (sizeof(ULONG
) * 2);
327 if (MaxValueLength
< DataSize
->LowPart
|| NextAttribute
->Type
!= AttributeEnd
)
329 DPRINT1("FIXME: Need to convert attribute to non-resident!\n");
330 return STATUS_NOT_IMPLEMENTED
;
334 else if (DataSize
->LowPart
< AttrContext
->Record
.Resident
.ValueLength
)
336 // we need to decrease the length
337 if (NextAttribute
->Type
!= AttributeEnd
)
339 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
340 return STATUS_NOT_IMPLEMENTED
;
344 InternalSetResidentAttributeLength(AttrContext
, FileRecord
, AttrOffset
, DataSize
->LowPart
);
347 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
349 // write the updated file record back to disk
350 Status
= UpdateFileRecord(Fcb
->Vcb
, Fcb
->MFTIndex
, FileRecord
);
352 if (NT_SUCCESS(Status
))
354 Fcb
->RFCB
.FileSize
= *DataSize
;
355 Fcb
->RFCB
.ValidDataLength
= *DataSize
;
356 CcSetFileSizes(FileObject
, (PCC_FILE_SIZES
)&Fcb
->RFCB
.AllocationSize
);
359 return STATUS_SUCCESS
;
363 ReadAttribute(PDEVICE_EXTENSION Vcb
,
364 PNTFS_ATTR_CONTEXT Context
,
371 LONGLONG DataRunOffset
;
372 ULONGLONG DataRunLength
;
373 LONGLONG DataRunStartLCN
;
374 ULONGLONG CurrentOffset
;
379 if (!Context
->Record
.IsNonResident
)
381 if (Offset
> Context
->Record
.Resident
.ValueLength
)
383 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
384 Length
= (ULONG
)(Context
->Record
.Resident
.ValueLength
- Offset
);
385 RtlCopyMemory(Buffer
, (PCHAR
)&Context
->Record
+ Context
->Record
.Resident
.ValueOffset
+ Offset
, Length
);
390 * Non-resident attribute
394 * I. Find the corresponding start data run.
399 // FIXME: Cache seems to be non-working. Disable it for now
400 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
403 DataRun
= Context
->CacheRun
;
404 LastLCN
= Context
->CacheRunLastLCN
;
405 DataRunStartLCN
= Context
->CacheRunStartLCN
;
406 DataRunLength
= Context
->CacheRunLength
;
407 CurrentOffset
= Context
->CacheRunCurrentOffset
;
412 DataRun
= (PUCHAR
)&Context
->Record
+ Context
->Record
.NonResident
.MappingPairsOffset
;
417 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
418 if (DataRunOffset
!= -1)
420 /* Normal data run. */
421 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
422 LastLCN
= DataRunStartLCN
;
426 /* Sparse data run. */
427 DataRunStartLCN
= -1;
430 if (Offset
>= CurrentOffset
&&
431 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
441 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
446 * II. Go through the run list and read the data
449 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
450 if (DataRunStartLCN
== -1)
452 RtlZeroMemory(Buffer
, ReadLength
);
453 Status
= STATUS_SUCCESS
;
457 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
458 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
,
460 Vcb
->NtfsInfo
.BytesPerSector
,
464 if (NT_SUCCESS(Status
))
466 Length
-= ReadLength
;
467 Buffer
+= ReadLength
;
468 AlreadyRead
+= ReadLength
;
470 if (ReadLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
472 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
473 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
474 if (DataRunOffset
!= (ULONGLONG
)-1)
476 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
477 LastLCN
= DataRunStartLCN
;
480 DataRunStartLCN
= -1;
485 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
486 if (DataRunStartLCN
== -1)
487 RtlZeroMemory(Buffer
, ReadLength
);
490 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
491 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
493 Vcb
->NtfsInfo
.BytesPerSector
,
496 if (!NT_SUCCESS(Status
))
500 Length
-= ReadLength
;
501 Buffer
+= ReadLength
;
502 AlreadyRead
+= ReadLength
;
504 /* We finished this request, but there still data in this data run. */
505 if (Length
== 0 && ReadLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
509 * Go to next run in the list.
514 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
515 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
516 if (DataRunOffset
!= -1)
518 /* Normal data run. */
519 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
520 LastLCN
= DataRunStartLCN
;
524 /* Sparse data run. */
525 DataRunStartLCN
= -1;
531 Context
->CacheRun
= DataRun
;
532 Context
->CacheRunOffset
= Offset
+ AlreadyRead
;
533 Context
->CacheRunStartLCN
= DataRunStartLCN
;
534 Context
->CacheRunLength
= DataRunLength
;
535 Context
->CacheRunLastLCN
= LastLCN
;
536 Context
->CacheRunCurrentOffset
= CurrentOffset
;
543 * @name WriteAttribute
546 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
547 * and it still needs more documentation / cleaning up.
550 * Volume Control Block indicating which volume to write the attribute to
553 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
556 * Offset, in bytes, from the beginning of the attribute indicating where to start
560 * The data that's being written to the device
563 * How much data will be written, in bytes
565 * @param RealLengthWritten
566 * Pointer to a ULONG which will receive how much data was written, in bytes
569 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
570 * writing to a sparse file.
572 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
573 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
578 WriteAttribute(PDEVICE_EXTENSION Vcb
,
579 PNTFS_ATTR_CONTEXT Context
,
583 PULONG RealLengthWritten
)
587 LONGLONG DataRunOffset
;
588 ULONGLONG DataRunLength
;
589 LONGLONG DataRunStartLCN
;
590 ULONGLONG CurrentOffset
;
593 PUCHAR SourceBuffer
= Buffer
;
594 LONGLONG StartingOffset
;
596 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb
, Context
, Offset
, Buffer
, Length
, RealLengthWritten
);
598 *RealLengthWritten
= 0;
600 // is this a resident attribute?
601 if (!Context
->Record
.IsNonResident
)
603 ULONG AttributeOffset
;
604 PNTFS_ATTR_CONTEXT FoundContext
;
605 PFILE_RECORD_HEADER FileRecord
;
607 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
609 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
610 return STATUS_INVALID_PARAMETER
;
613 FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
617 DPRINT1("Error: Couldn't allocate file record!\n");
618 return STATUS_NO_MEMORY
;
621 // read the file record
622 ReadFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
624 // find where to write the attribute data to
625 Status
= FindAttribute(Vcb
, FileRecord
,
626 Context
->Record
.Type
,
627 (PCWSTR
)((PCHAR
)&Context
->Record
+ Context
->Record
.NameOffset
),
628 Context
->Record
.NameLength
,
632 if (!NT_SUCCESS(Status
))
634 DPRINT1("ERROR: Couldn't find matching attribute!\n");
635 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
639 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset
, AttributeOffset
, Context
->Record
.Resident
.ValueLength
);
640 Offset
+= AttributeOffset
+ Context
->Record
.Resident
.ValueOffset
;
642 if (Offset
+ Length
> Vcb
->NtfsInfo
.BytesPerFileRecord
)
644 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
645 ReleaseAttributeContext(FoundContext
);
646 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
647 return STATUS_INVALID_PARAMETER
;
650 // copy the data being written into the file record
651 RtlCopyMemory((PCHAR
)FileRecord
+ Offset
, Buffer
, Length
);
653 Status
= UpdateFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
655 ReleaseAttributeContext(FoundContext
);
656 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
658 if (NT_SUCCESS(Status
))
659 *RealLengthWritten
= Length
;
664 // This is a non-resident attribute.
666 // I. Find the corresponding start data run.
668 // FIXME: Cache seems to be non-working. Disable it for now
669 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
672 DataRun = Context->CacheRun;
673 LastLCN = Context->CacheRunLastLCN;
674 DataRunStartLCN = Context->CacheRunStartLCN;
675 DataRunLength = Context->CacheRunLength;
676 CurrentOffset = Context->CacheRunCurrentOffset;
681 DataRun
= (PUCHAR
)&Context
->Record
+ Context
->Record
.NonResident
.MappingPairsOffset
;
686 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
687 if (DataRunOffset
!= -1)
690 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
691 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
692 LastLCN
= DataRunStartLCN
;
696 // Sparse data run. We can't support writing to sparse files yet
697 // (it may require increasing the allocation size).
698 DataRunStartLCN
= -1;
699 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
700 return STATUS_NOT_IMPLEMENTED
;
703 // Have we reached the data run we're trying to write to?
704 if (Offset
>= CurrentOffset
&&
705 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
712 // We reached the last assigned cluster
713 // TODO: assign new clusters to the end of the file.
714 // (Presently, this code will never be reached, the write should have already failed by now)
715 return STATUS_END_OF_FILE
;
718 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
722 // II. Go through the run list and write the data
724 /* REVIEWME -- As adapted from NtfsReadAttribute():
725 We seem to be making a special case for the first applicable data run, but I'm not sure why.
726 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
728 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
730 StartingOffset
= DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
;
732 // Write the data to the disk
733 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
736 Vcb
->NtfsInfo
.BytesPerSector
,
737 (PVOID
)SourceBuffer
);
739 // Did the write fail?
740 if (!NT_SUCCESS(Status
))
742 Context
->CacheRun
= DataRun
;
743 Context
->CacheRunOffset
= Offset
;
744 Context
->CacheRunStartLCN
= DataRunStartLCN
;
745 Context
->CacheRunLength
= DataRunLength
;
746 Context
->CacheRunLastLCN
= LastLCN
;
747 Context
->CacheRunCurrentOffset
= CurrentOffset
;
752 Length
-= WriteLength
;
753 SourceBuffer
+= WriteLength
;
754 *RealLengthWritten
+= WriteLength
;
756 // Did we write to the end of the data run?
757 if (WriteLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
759 // Advance to the next data run
760 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
761 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
763 if (DataRunOffset
!= (ULONGLONG
)-1)
765 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
766 LastLCN
= DataRunStartLCN
;
769 DataRunStartLCN
= -1;
772 // Do we have more data to write?
775 // Make sure we don't write past the end of the current data run
776 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
778 // Are we dealing with a sparse data run?
779 if (DataRunStartLCN
== -1)
781 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
782 return STATUS_NOT_IMPLEMENTED
;
786 // write the data to the disk
787 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
788 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
790 Vcb
->NtfsInfo
.BytesPerSector
,
791 (PVOID
)SourceBuffer
);
792 if (!NT_SUCCESS(Status
))
796 Length
-= WriteLength
;
797 SourceBuffer
+= WriteLength
;
798 *RealLengthWritten
+= WriteLength
;
800 // We finished this request, but there's still data in this data run.
801 if (Length
== 0 && WriteLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
804 // Go to next run in the list.
808 // that was the last run
811 // Failed sanity check.
812 DPRINT1("Encountered EOF before expected!\n");
813 return STATUS_END_OF_FILE
;
819 // Advance to the next data run
820 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
821 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
822 if (DataRunOffset
!= -1)
825 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
826 LastLCN
= DataRunStartLCN
;
831 DataRunStartLCN
= -1;
833 } // end while (Length > 0) [more data to write]
835 Context
->CacheRun
= DataRun
;
836 Context
->CacheRunOffset
= Offset
+ *RealLengthWritten
;
837 Context
->CacheRunStartLCN
= DataRunStartLCN
;
838 Context
->CacheRunLength
= DataRunLength
;
839 Context
->CacheRunLastLCN
= LastLCN
;
840 Context
->CacheRunCurrentOffset
= CurrentOffset
;
846 ReadFileRecord(PDEVICE_EXTENSION Vcb
,
848 PFILE_RECORD_HEADER file
)
852 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb
, index
, file
);
854 BytesRead
= ReadAttribute(Vcb
, Vcb
->MFTContext
, index
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (PCHAR
)file
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
855 if (BytesRead
!= Vcb
->NtfsInfo
.BytesPerFileRecord
)
857 DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
858 return STATUS_PARTIAL_COPY
;
861 /* Apply update sequence array fixups. */
862 DPRINT("Sequence number: %u\n", file
->SequenceNumber
);
863 return FixupUpdateSequenceArray(Vcb
, &file
->Ntfs
);
868 * Searches a file's parent directory (given the parent's index in the mft)
869 * for the given file. Upon finding an index entry for that file, updates
870 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
872 * (Most of this code was copied from NtfsFindMftRecord)
875 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb
,
876 ULONGLONG ParentMFTIndex
,
877 PUNICODE_STRING FileName
,
879 ULONGLONG NewDataSize
,
880 ULONGLONG NewAllocationSize
)
882 PFILE_RECORD_HEADER MftRecord
;
883 PNTFS_ATTR_CONTEXT IndexRootCtx
;
884 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
886 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
888 ULONG CurrentEntry
= 0;
890 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u)\n", Vcb
, ParentMFTIndex
, FileName
, DirSearch
, NewDataSize
, NewAllocationSize
);
892 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
893 Vcb
->NtfsInfo
.BytesPerFileRecord
,
895 if (MftRecord
== NULL
)
897 return STATUS_INSUFFICIENT_RESOURCES
;
900 Status
= ReadFileRecord(Vcb
, ParentMFTIndex
, MftRecord
);
901 if (!NT_SUCCESS(Status
))
903 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
907 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
908 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
909 if (!NT_SUCCESS(Status
))
911 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
915 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
916 if (IndexRecord
== NULL
)
918 ReleaseAttributeContext(IndexRootCtx
);
919 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
920 return STATUS_INSUFFICIENT_RESOURCES
;
923 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
924 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
925 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
926 // Index root is always resident.
927 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
929 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
931 Status
= UpdateIndexEntryFileNameSize(Vcb
,
934 IndexRoot
->SizeOfEntry
,
944 ReleaseAttributeContext(IndexRootCtx
);
945 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
946 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
952 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
953 * proper index entry.
954 * (Heavily based on BrowseIndexEntries)
957 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb
,
958 PFILE_RECORD_HEADER MftRecord
,
960 ULONG IndexBlockSize
,
961 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
962 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
963 PUNICODE_STRING FileName
,
967 ULONGLONG NewDataSize
,
968 ULONGLONG NewAllocatedSize
)
972 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
973 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
974 ULONGLONG IndexAllocationSize
;
975 PINDEX_BUFFER IndexBuffer
;
977 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
);
979 // find the index entry responsible for the file we're trying to update
980 IndexEntry
= FirstEntry
;
981 while (IndexEntry
< LastEntry
&&
982 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
984 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > 0x10 &&
985 *CurrentEntry
>= *StartEntry
&&
986 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
987 CompareFileName(FileName
, IndexEntry
, DirSearch
))
989 *StartEntry
= *CurrentEntry
;
990 IndexEntry
->FileName
.DataSize
= NewDataSize
;
991 IndexEntry
->FileName
.AllocatedSize
= NewAllocatedSize
;
992 // indicate that the caller will still need to write the structure to the disk
993 return STATUS_PENDING
;
996 (*CurrentEntry
) += 1;
997 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
998 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1001 /* If we're already browsing a subnode */
1002 if (IndexRecord
== NULL
)
1004 return STATUS_OBJECT_PATH_NOT_FOUND
;
1007 /* If there's no subnode */
1008 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1010 return STATUS_OBJECT_PATH_NOT_FOUND
;
1013 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1014 if (!NT_SUCCESS(Status
))
1016 DPRINT("Corrupted filesystem!\n");
1020 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
1021 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1022 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1024 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1025 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1026 if (!NT_SUCCESS(Status
))
1031 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1032 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1033 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1034 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1035 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1036 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1038 Status
= UpdateIndexEntryFileNameSize(NULL
, NULL
, NULL
, 0, FirstEntry
, LastEntry
, FileName
, StartEntry
, CurrentEntry
, DirSearch
, NewDataSize
, NewAllocatedSize
);
1039 if (Status
== STATUS_PENDING
)
1041 // write the index record back to disk
1044 // first we need to update the fixup values for the index block
1045 Status
= AddFixupArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1046 if (!NT_SUCCESS(Status
))
1048 DPRINT1("Error: Failed to update fixup sequence array!\n");
1052 Status
= WriteAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, (const PUCHAR
)IndexRecord
, IndexBlockSize
, &Written
);
1053 if (!NT_SUCCESS(Status
))
1055 DPRINT1("ERROR Performing write!\n");
1059 Status
= STATUS_SUCCESS
;
1062 if (NT_SUCCESS(Status
))
1068 ReleaseAttributeContext(IndexAllocationCtx
);
1075 * Writes a file record to the master file table, at a given index.
1078 UpdateFileRecord(PDEVICE_EXTENSION Vcb
,
1080 PFILE_RECORD_HEADER file
)
1083 NTSTATUS Status
= STATUS_SUCCESS
;
1085 DPRINT("UpdateFileRecord(%p, %I64x, %p)\n", Vcb
, index
, file
);
1087 // Add the fixup array to prepare the data for writing to disk
1088 AddFixupArray(Vcb
, &file
->Ntfs
);
1090 // write the file record to the master file table
1091 Status
= WriteAttribute(Vcb
, Vcb
->MFTContext
, index
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (const PUCHAR
)file
, Vcb
->NtfsInfo
.BytesPerFileRecord
, &BytesWritten
);
1093 if (!NT_SUCCESS(Status
))
1095 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1098 // remove the fixup array (so the file record pointer can still be used)
1099 FixupUpdateSequenceArray(Vcb
, &file
->Ntfs
);
1106 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb
,
1107 PNTFS_RECORD_HEADER Record
)
1114 USA
= (USHORT
*)((PCHAR
)Record
+ Record
->UsaOffset
);
1115 USANumber
= *(USA
++);
1116 USACount
= Record
->UsaCount
- 1; /* Exclude the USA Number. */
1117 Block
= (USHORT
*)((PCHAR
)Record
+ Vcb
->NtfsInfo
.BytesPerSector
- 2);
1119 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb
, Record
, USANumber
, USACount
);
1123 if (*Block
!= USANumber
)
1125 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block
, USANumber
);
1126 return STATUS_UNSUCCESSFUL
;
1129 Block
= (USHORT
*)((PCHAR
)Block
+ Vcb
->NtfsInfo
.BytesPerSector
);
1133 return STATUS_SUCCESS
;
1137 AddFixupArray(PDEVICE_EXTENSION Vcb
,
1138 PNTFS_RECORD_HEADER Record
)
1140 USHORT
*pShortToFixUp
;
1141 unsigned int ArrayEntryCount
= Record
->UsaCount
- 1;
1142 unsigned int Offset
= Vcb
->NtfsInfo
.BytesPerSector
- 2;
1145 PFIXUP_ARRAY fixupArray
= (PFIXUP_ARRAY
)((UCHAR
*)Record
+ Record
->UsaOffset
);
1147 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb
, Record
, fixupArray
->USN
, ArrayEntryCount
);
1151 for (i
= 0; i
< ArrayEntryCount
; i
++)
1153 DPRINT("USN: %u\tOffset: %u\n", fixupArray
->USN
, Offset
);
1155 pShortToFixUp
= (USHORT
*)((PCHAR
)Record
+ Offset
);
1156 fixupArray
->Array
[i
] = *pShortToFixUp
;
1157 *pShortToFixUp
= fixupArray
->USN
;
1158 Offset
+= Vcb
->NtfsInfo
.BytesPerSector
;
1161 return STATUS_SUCCESS
;
1165 ReadLCN(PDEVICE_EXTENSION Vcb
,
1170 LARGE_INTEGER DiskSector
;
1172 DiskSector
.QuadPart
= lcn
;
1174 return NtfsReadSectors(Vcb
->StorageDevice
,
1175 DiskSector
.u
.LowPart
* Vcb
->NtfsInfo
.SectorsPerCluster
,
1176 count
* Vcb
->NtfsInfo
.SectorsPerCluster
,
1177 Vcb
->NtfsInfo
.BytesPerSector
,
1184 CompareFileName(PUNICODE_STRING FileName
,
1185 PINDEX_ENTRY_ATTRIBUTE IndexEntry
,
1188 BOOLEAN Ret
, Alloc
= FALSE
;
1189 UNICODE_STRING EntryName
;
1191 EntryName
.Buffer
= IndexEntry
->FileName
.Name
;
1193 EntryName
.MaximumLength
= IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
1197 UNICODE_STRING IntFileName
;
1198 if (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
)
1200 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName
, FileName
, TRUE
)));
1205 IntFileName
= *FileName
;
1208 Ret
= FsRtlIsNameInExpression(&IntFileName
, &EntryName
, (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
), NULL
);
1212 RtlFreeUnicodeString(&IntFileName
);
1219 return (RtlCompareUnicodeString(FileName
, &EntryName
, (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
)) == 0);
1226 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry
)
1228 DPRINT1("Entry: %p\n", IndexEntry
);
1229 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry
->Data
.Directory
.IndexedFile
);
1230 DPRINT1("\tLength: %u\n", IndexEntry
->Length
);
1231 DPRINT1("\tKeyLength: %u\n", IndexEntry
->KeyLength
);
1232 DPRINT1("\tFlags: %x\n", IndexEntry
->Flags
);
1233 DPRINT1("\tReserved: %x\n", IndexEntry
->Reserved
);
1234 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry
->FileName
.DirectoryFileReferenceNumber
);
1235 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry
->FileName
.CreationTime
);
1236 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry
->FileName
.ChangeTime
);
1237 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry
->FileName
.LastWriteTime
);
1238 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry
->FileName
.LastAccessTime
);
1239 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry
->FileName
.AllocatedSize
);
1240 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry
->FileName
.DataSize
);
1241 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry
->FileName
.FileAttributes
);
1242 DPRINT1("\t\tNameLength: %u\n", IndexEntry
->FileName
.NameLength
);
1243 DPRINT1("\t\tNameType: %x\n", IndexEntry
->FileName
.NameType
);
1244 DPRINT1("\t\tName: %.*S\n", IndexEntry
->FileName
.NameLength
, IndexEntry
->FileName
.Name
);
1249 BrowseIndexEntries(PDEVICE_EXTENSION Vcb
,
1250 PFILE_RECORD_HEADER MftRecord
,
1252 ULONG IndexBlockSize
,
1253 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1254 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1255 PUNICODE_STRING FileName
,
1257 PULONG CurrentEntry
,
1259 ULONGLONG
*OutMFTIndex
)
1263 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1264 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1265 ULONGLONG IndexAllocationSize
;
1266 PINDEX_BUFFER IndexBuffer
;
1268 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
);
1270 IndexEntry
= FirstEntry
;
1271 while (IndexEntry
< LastEntry
&&
1272 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1274 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > 0x10 &&
1275 *CurrentEntry
>= *StartEntry
&&
1276 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1277 CompareFileName(FileName
, IndexEntry
, DirSearch
))
1279 *StartEntry
= *CurrentEntry
;
1280 *OutMFTIndex
= (IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
);
1281 return STATUS_SUCCESS
;
1284 (*CurrentEntry
) += 1;
1285 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1286 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1289 /* If we're already browsing a subnode */
1290 if (IndexRecord
== NULL
)
1292 return STATUS_OBJECT_PATH_NOT_FOUND
;
1295 /* If there's no subnode */
1296 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1298 return STATUS_OBJECT_PATH_NOT_FOUND
;
1301 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1302 if (!NT_SUCCESS(Status
))
1304 DPRINT("Corrupted filesystem!\n");
1308 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
1309 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1310 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1312 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1313 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1314 if (!NT_SUCCESS(Status
))
1319 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1320 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1321 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1322 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1323 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1324 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1326 Status
= BrowseIndexEntries(NULL
, NULL
, NULL
, 0, FirstEntry
, LastEntry
, FileName
, StartEntry
, CurrentEntry
, DirSearch
, OutMFTIndex
);
1327 if (NT_SUCCESS(Status
))
1333 ReleaseAttributeContext(IndexAllocationCtx
);
1338 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb
,
1340 PUNICODE_STRING FileName
,
1343 ULONGLONG
*OutMFTIndex
)
1345 PFILE_RECORD_HEADER MftRecord
;
1346 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1347 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1349 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1351 ULONG CurrentEntry
= 0;
1353 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb
, MFTIndex
, FileName
, *FirstEntry
, DirSearch
, OutMFTIndex
);
1355 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1356 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1358 if (MftRecord
== NULL
)
1360 return STATUS_INSUFFICIENT_RESOURCES
;
1363 Status
= ReadFileRecord(Vcb
, MFTIndex
, MftRecord
);
1364 if (!NT_SUCCESS(Status
))
1366 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1370 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
1371 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
1372 if (!NT_SUCCESS(Status
))
1374 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1378 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
1379 if (IndexRecord
== NULL
)
1381 ReleaseAttributeContext(IndexRootCtx
);
1382 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1383 return STATUS_INSUFFICIENT_RESOURCES
;
1386 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
1387 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
1388 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
1389 /* Index root is always resident. */
1390 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
1391 ReleaseAttributeContext(IndexRootCtx
);
1393 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
1395 Status
= BrowseIndexEntries(Vcb
, MftRecord
, IndexRecord
, IndexRoot
->SizeOfEntry
, IndexEntry
, IndexEntryEnd
, FileName
, FirstEntry
, &CurrentEntry
, DirSearch
, OutMFTIndex
);
1397 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1398 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1404 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb
,
1405 PUNICODE_STRING PathName
,
1406 PFILE_RECORD_HEADER
*FileRecord
,
1407 PULONGLONG MFTIndex
,
1408 ULONGLONG CurrentMFTIndex
)
1410 UNICODE_STRING Current
, Remaining
;
1412 ULONG FirstEntry
= 0;
1414 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb
, PathName
, FileRecord
, CurrentMFTIndex
);
1416 FsRtlDissectName(*PathName
, &Current
, &Remaining
);
1418 while (Current
.Length
!= 0)
1420 DPRINT("Current: %wZ\n", &Current
);
1422 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, &Current
, &FirstEntry
, FALSE
, &CurrentMFTIndex
);
1423 if (!NT_SUCCESS(Status
))
1428 if (Remaining
.Length
== 0)
1431 FsRtlDissectName(Current
, &Current
, &Remaining
);
1434 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1435 if (*FileRecord
== NULL
)
1437 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
1438 return STATUS_INSUFFICIENT_RESOURCES
;
1441 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
1442 if (!NT_SUCCESS(Status
))
1444 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
1445 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
1449 *MFTIndex
= CurrentMFTIndex
;
1451 return STATUS_SUCCESS
;
1455 NtfsLookupFile(PDEVICE_EXTENSION Vcb
,
1456 PUNICODE_STRING PathName
,
1457 PFILE_RECORD_HEADER
*FileRecord
,
1458 PULONGLONG MFTIndex
)
1460 return NtfsLookupFileAt(Vcb
, PathName
, FileRecord
, MFTIndex
, NTFS_FILE_ROOT
);
1464 * @name NtfsDumpFileRecord
1467 * Provides diagnostic information about a file record. Prints a hex dump
1468 * of the entire record (based on the size reported by FileRecord->ByesInUse),
1469 * then prints a dump of each attribute.
1472 * Pointer to a DEVICE_EXTENSION describing the volume.
1475 * Pointer to the file record to be analyzed.
1478 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
1479 * in size, and not just the header.
1483 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb
,
1484 PFILE_RECORD_HEADER FileRecord
)
1488 // dump binary data, 8 bytes at a time
1489 for (i
= 0; i
< FileRecord
->BytesInUse
; i
+= 8)
1491 // display current offset, in hex
1492 DbgPrint("\t%03x\t", i
);
1494 // display hex value of each of the next 8 bytes
1495 for (j
= 0; j
< 8; j
++)
1496 DbgPrint("%02x ", *(PUCHAR
)((ULONG_PTR
)FileRecord
+ i
+ j
));
1500 NtfsDumpFileAttributes(Vcb
, FileRecord
);
1504 NtfsFindFileAt(PDEVICE_EXTENSION Vcb
,
1505 PUNICODE_STRING SearchPattern
,
1507 PFILE_RECORD_HEADER
*FileRecord
,
1508 PULONGLONG MFTIndex
,
1509 ULONGLONG CurrentMFTIndex
)
1513 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb
, SearchPattern
, *FirstEntry
, FileRecord
, MFTIndex
, CurrentMFTIndex
);
1515 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, SearchPattern
, FirstEntry
, TRUE
, &CurrentMFTIndex
);
1516 if (!NT_SUCCESS(Status
))
1518 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status
);
1522 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1523 if (*FileRecord
== NULL
)
1525 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
1526 return STATUS_INSUFFICIENT_RESOURCES
;
1529 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
1530 if (!NT_SUCCESS(Status
))
1532 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
1533 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
1537 *MFTIndex
= CurrentMFTIndex
;
1539 return STATUS_SUCCESS
;