3 * Copyright (C) 2002, 2014 ReactOS Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19 * COPYRIGHT: See COPYING in the top level directory
20 * PROJECT: ReactOS kernel
21 * FILE: drivers/filesystem/ntfs/mft.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Eric Kohl
25 * Pierre Schweitzer (pierre@reactos.org)
26 * Hervé Poussineau (hpoussin@reactos.org)
30 /* INCLUDES *****************************************************************/
37 /* FUNCTIONS ****************************************************************/
40 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord
)
42 PNTFS_ATTR_CONTEXT Context
;
44 Context
= ExAllocatePoolWithTag(NonPagedPool
,
45 FIELD_OFFSET(NTFS_ATTR_CONTEXT
, Record
) + AttrRecord
->Length
,
47 RtlCopyMemory(&Context
->Record
, AttrRecord
, AttrRecord
->Length
);
48 if (AttrRecord
->IsNonResident
)
50 LONGLONG DataRunOffset
;
51 ULONGLONG DataRunLength
;
52 ULONGLONG NextVBN
= 0;
53 PUCHAR DataRun
= (PUCHAR
)&Context
->Record
+ Context
->Record
.NonResident
.MappingPairsOffset
;
55 Context
->CacheRun
= DataRun
;
56 Context
->CacheRunOffset
= 0;
57 Context
->CacheRun
= DecodeRun(Context
->CacheRun
, &DataRunOffset
, &DataRunLength
);
58 Context
->CacheRunLength
= DataRunLength
;
59 if (DataRunOffset
!= -1)
62 Context
->CacheRunStartLCN
=
63 Context
->CacheRunLastLCN
= DataRunOffset
;
68 Context
->CacheRunStartLCN
= -1;
69 Context
->CacheRunLastLCN
= 0;
71 Context
->CacheRunCurrentOffset
= 0;
73 // Convert the data runs to a map control block
74 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun
, &Context
->DataRunsMCB
, &NextVBN
)))
76 DPRINT1("Unable to convert data runs to MCB!\n");
77 ExFreePoolWithTag(Context
, TAG_NTFS
);
87 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context
)
89 if (Context
->Record
.IsNonResident
)
91 FsRtlUninitializeLargeMcb(&Context
->DataRunsMCB
);
94 ExFreePoolWithTag(Context
, TAG_NTFS
);
102 * Searches a file record for an attribute matching the given type and name.
105 * Optional pointer to a ULONG that will receive the offset of the found attribute
106 * from the beginning of the record. Can be set to NULL.
109 FindAttribute(PDEVICE_EXTENSION Vcb
,
110 PFILE_RECORD_HEADER MftRecord
,
114 PNTFS_ATTR_CONTEXT
* AttrCtx
,
119 FIND_ATTR_CONTXT Context
;
120 PNTFS_ATTR_RECORD Attribute
;
122 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %u, %p)\n", Vcb
, MftRecord
, Type
, Name
, NameLength
, AttrCtx
);
125 Status
= FindFirstAttribute(&Context
, Vcb
, MftRecord
, FALSE
, &Attribute
);
126 while (NT_SUCCESS(Status
))
128 if (Attribute
->Type
== Type
&& Attribute
->NameLength
== NameLength
)
134 AttrName
= (PWCHAR
)((PCHAR
)Attribute
+ Attribute
->NameOffset
);
135 DPRINT("%.*S, %.*S\n", Attribute
->NameLength
, AttrName
, NameLength
, Name
);
136 if (RtlCompareMemory(AttrName
, Name
, NameLength
<< 1) == (NameLength
<< 1))
148 /* Found it, fill up the context and return. */
149 DPRINT("Found context\n");
150 *AttrCtx
= PrepareAttributeContext(Attribute
);
152 (*AttrCtx
)->FileMFTIndex
= MftRecord
->MFTRecordNumber
;
155 *Offset
= Context
.Offset
;
157 FindCloseAttribute(&Context
);
158 return STATUS_SUCCESS
;
162 Status
= FindNextAttribute(&Context
, &Attribute
);
165 FindCloseAttribute(&Context
);
166 return STATUS_OBJECT_NAME_NOT_FOUND
;
171 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord
)
173 if (AttrRecord
->IsNonResident
)
174 return AttrRecord
->NonResident
.AllocatedSize
;
176 return AttrRecord
->Resident
.ValueLength
;
181 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord
)
183 if (AttrRecord
->IsNonResident
)
184 return AttrRecord
->NonResident
.DataSize
;
186 return AttrRecord
->Resident
.ValueLength
;
190 InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext
,
191 PFILE_RECORD_HEADER FileRecord
,
195 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
196 ULONG NextAttributeOffset
;
198 DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext
, FileRecord
, AttrOffset
, DataSize
);
200 // update ValueLength Field
201 AttrContext
->Record
.Resident
.ValueLength
=
202 Destination
->Resident
.ValueLength
= DataSize
;
204 // calculate the record length and end marker offset
205 AttrContext
->Record
.Length
=
206 Destination
->Length
= DataSize
+ AttrContext
->Record
.Resident
.ValueOffset
;
207 NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
209 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
210 if (NextAttributeOffset
% 8 != 0)
212 USHORT Padding
= 8 - (NextAttributeOffset
% 8);
213 NextAttributeOffset
+= Padding
;
214 AttrContext
->Record
.Length
+= Padding
;
215 Destination
->Length
+= Padding
;
218 // advance Destination to the final "attribute" and set the file record end
219 Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Destination
+ Destination
->Length
);
220 SetFileRecordEnd(FileRecord
, Destination
, FILE_RECORD_END
);
224 * @parameter FileRecord
225 * Pointer to a file record. Must be a full record at least
226 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
229 SetAttributeDataLength(PFILE_OBJECT FileObject
,
231 PNTFS_ATTR_CONTEXT AttrContext
,
233 PFILE_RECORD_HEADER FileRecord
,
234 PLARGE_INTEGER DataSize
)
236 NTSTATUS Status
= STATUS_SUCCESS
;
237 ULONG BytesPerCluster
= Fcb
->Vcb
->NtfsInfo
.BytesPerCluster
;
239 // are we truncating the file?
240 if (DataSize
->QuadPart
< AttributeDataLength(&AttrContext
->Record
))
242 if (!MmCanFileBeTruncated(FileObject
->SectionObjectPointer
, DataSize
))
244 DPRINT1("Can't truncate a memory-mapped file!\n");
245 return STATUS_USER_MAPPED_FILE
;
249 if (AttrContext
->Record
.IsNonResident
)
251 ULONGLONG AllocationSize
= ROUND_UP(DataSize
->QuadPart
, BytesPerCluster
);
252 PNTFS_ATTR_RECORD DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
253 ULONG ExistingClusters
= AttrContext
->Record
.NonResident
.AllocatedSize
/ BytesPerCluster
;
255 // do we need to increase the allocation size?
256 if (AttrContext
->Record
.NonResident
.AllocatedSize
< AllocationSize
)
258 ULONG ClustersNeeded
= (AllocationSize
/ BytesPerCluster
) - ExistingClusters
;
259 LARGE_INTEGER LastClusterInDataRun
;
260 ULONG NextAssignedCluster
;
261 ULONG AssignedClusters
;
263 if (ExistingClusters
== 0)
265 LastClusterInDataRun
.QuadPart
= 0;
269 if (!FsRtlLookupLargeMcbEntry(&AttrContext
->DataRunsMCB
,
270 (LONGLONG
)AttrContext
->Record
.NonResident
.HighestVCN
,
271 (PLONGLONG
)&LastClusterInDataRun
.QuadPart
,
277 DPRINT1("Error looking up final large MCB entry!\n");
279 // Most likely, HighestVCN went above the largest mapping
280 DPRINT1("Highest VCN of record: %I64u\n", AttrContext
->Record
.NonResident
.HighestVCN
);
281 return STATUS_INVALID_PARAMETER
;
285 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun
.QuadPart
);
286 DPRINT("Highest VCN of record: %I64u\n", AttrContext
->Record
.NonResident
.HighestVCN
);
288 while (ClustersNeeded
> 0)
290 Status
= NtfsAllocateClusters(Fcb
->Vcb
,
291 LastClusterInDataRun
.LowPart
+ 1,
293 &NextAssignedCluster
,
296 if (!NT_SUCCESS(Status
))
298 DPRINT1("Error: Unable to allocate requested clusters!\n");
302 // now we need to add the clusters we allocated to the data run
303 Status
= AddRun(Fcb
->Vcb
, AttrContext
, AttrOffset
, FileRecord
, NextAssignedCluster
, AssignedClusters
);
304 if (!NT_SUCCESS(Status
))
306 DPRINT1("Error: Unable to add data run!\n");
310 ClustersNeeded
-= AssignedClusters
;
311 LastClusterInDataRun
.LowPart
= NextAssignedCluster
+ AssignedClusters
- 1;
314 else if (AttrContext
->Record
.NonResident
.AllocatedSize
> AllocationSize
)
316 // shrink allocation size
317 ULONG ClustersToFree
= ExistingClusters
- (AllocationSize
/ BytesPerCluster
);
318 Status
= FreeClusters(Fcb
->Vcb
, AttrContext
, AttrOffset
, FileRecord
, ClustersToFree
);
321 // TODO: is the file compressed, encrypted, or sparse?
323 // NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource
325 Fcb
->RFCB
.AllocationSize
.QuadPart
= AllocationSize
;
326 AttrContext
->Record
.NonResident
.AllocatedSize
= AllocationSize
;
327 AttrContext
->Record
.NonResident
.DataSize
= DataSize
->QuadPart
;
328 AttrContext
->Record
.NonResident
.InitializedSize
= DataSize
->QuadPart
;
330 DestinationAttribute
->NonResident
.AllocatedSize
= AllocationSize
;
331 DestinationAttribute
->NonResident
.DataSize
= DataSize
->QuadPart
;
332 DestinationAttribute
->NonResident
.InitializedSize
= DataSize
->QuadPart
;
334 DPRINT("Allocated Size: %I64u\n", DestinationAttribute
->NonResident
.AllocatedSize
);
338 // resident attribute
340 // find the next attribute
341 ULONG NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
342 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((PCHAR
)FileRecord
+ NextAttributeOffset
);
344 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
346 // Do we need to increase the data length?
347 if (DataSize
->QuadPart
> AttrContext
->Record
.Resident
.ValueLength
)
349 // There's usually padding at the end of a record. Do we need to extend past it?
350 ULONG MaxValueLength
= AttrContext
->Record
.Length
- AttrContext
->Record
.Resident
.ValueOffset
;
351 if (MaxValueLength
< DataSize
->LowPart
)
353 // If this is the last attribute, we could move the end marker to the very end of the file record
354 MaxValueLength
+= Fcb
->Vcb
->NtfsInfo
.BytesPerFileRecord
- NextAttributeOffset
- (sizeof(ULONG
) * 2);
356 if (MaxValueLength
< DataSize
->LowPart
|| NextAttribute
->Type
!= AttributeEnd
)
358 // convert attribute to non-resident
359 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
360 LARGE_INTEGER AttribDataSize
;
362 ULONG EndAttributeOffset
;
365 DPRINT1("Converting attribute to non-resident.\n");
367 AttribDataSize
.QuadPart
= AttrContext
->Record
.Resident
.ValueLength
;
369 // Is there existing data we need to back-up?
370 if (AttribDataSize
.QuadPart
> 0)
372 AttribData
= ExAllocatePoolWithTag(NonPagedPool
, AttribDataSize
.QuadPart
, TAG_NTFS
);
373 if (AttribData
== NULL
)
375 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
376 return STATUS_INSUFFICIENT_RESOURCES
;
379 // read data to temp buffer
380 Status
= ReadAttribute(Fcb
->Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
);
381 if (!NT_SUCCESS(Status
))
383 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
384 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
389 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
391 // Zero out the NonResident structure
392 RtlZeroMemory(&AttrContext
->Record
.NonResident
.LowestVCN
,
393 FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.CompressedSize
) - FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.LowestVCN
));
394 RtlZeroMemory(&Destination
->NonResident
.LowestVCN
,
395 FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.CompressedSize
) - FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.LowestVCN
));
397 // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
398 AttrContext
->Record
.NonResident
.MappingPairsOffset
= Destination
->NonResident
.MappingPairsOffset
= 0x40 + (Destination
->NameLength
* 2);
400 // mark the attribute as non-resident
401 AttrContext
->Record
.IsNonResident
= Destination
->IsNonResident
= 1;
403 // update the end of the file record
404 // calculate position of end markers (1 byte for empty data run)
405 EndAttributeOffset
= AttrOffset
+ AttrContext
->Record
.NonResident
.MappingPairsOffset
+ 1;
406 EndAttributeOffset
= ALIGN_UP_BY(EndAttributeOffset
, 8);
409 Destination
->Length
= EndAttributeOffset
- AttrOffset
;
410 AttrContext
->Record
.Length
= Destination
->Length
;
412 // Update the file record end
413 SetFileRecordEnd(FileRecord
,
414 (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ EndAttributeOffset
),
417 // update file record on disk
418 Status
= UpdateFileRecord(Fcb
->Vcb
, AttrContext
->FileMFTIndex
, FileRecord
);
419 if (!NT_SUCCESS(Status
))
421 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
422 if (AttribDataSize
.QuadPart
> 0)
423 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
427 // Initialize the MCB, potentially catch an exception
429 FsRtlInitializeLargeMcb(&AttrContext
->DataRunsMCB
, NonPagedPool
);
430 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
) {
431 _SEH2_YIELD(return _SEH2_GetExceptionCode());
434 // Now we can treat the attribute as non-resident and enlarge it normally
435 Status
= SetAttributeDataLength(FileObject
, Fcb
, AttrContext
, AttrOffset
, FileRecord
, DataSize
);
436 if (!NT_SUCCESS(Status
))
438 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
439 if (AttribDataSize
.QuadPart
> 0)
440 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
444 // restore the back-up attribute, if we made one
445 if (AttribDataSize
.QuadPart
> 0)
447 Status
= WriteAttribute(Fcb
->Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
, &LengthWritten
);
448 if (!NT_SUCCESS(Status
))
450 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
451 // TODO: Reverse migration so no data is lost
452 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
456 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
461 else if (DataSize
->LowPart
< AttrContext
->Record
.Resident
.ValueLength
)
463 // we need to decrease the length
464 if (NextAttribute
->Type
!= AttributeEnd
)
466 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
467 return STATUS_NOT_IMPLEMENTED
;
471 // set the new length of the resident attribute (if we didn't migrate it)
472 if(!AttrContext
->Record
.IsNonResident
)
473 InternalSetResidentAttributeLength(AttrContext
, FileRecord
, AttrOffset
, DataSize
->LowPart
);
476 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
478 // write the updated file record back to disk
479 Status
= UpdateFileRecord(Fcb
->Vcb
, Fcb
->MFTIndex
, FileRecord
);
481 if (NT_SUCCESS(Status
))
483 Fcb
->RFCB
.FileSize
= *DataSize
;
484 Fcb
->RFCB
.ValidDataLength
= *DataSize
;
485 CcSetFileSizes(FileObject
, (PCC_FILE_SIZES
)&Fcb
->RFCB
.AllocationSize
);
488 return STATUS_SUCCESS
;
492 * @name SetFileRecordEnd
495 * This small function sets a new endpoint for the file record. It set's the final
496 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
499 * Pointer to the file record whose endpoint (length) will be set.
502 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
503 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
506 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
507 * a file record, it preserves the final ULONG that previously ended the record, even though this
508 * value is (to my knowledge) never used. We emulate this behavior.
512 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord
,
513 PNTFS_ATTR_RECORD AttrEnd
,
516 // mark the end of attributes
517 AttrEnd
->Type
= AttributeEnd
;
519 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
520 AttrEnd
->Length
= EndMarker
;
522 // recalculate bytes in use
523 FileRecord
->BytesInUse
= (ULONG_PTR
)AttrEnd
- (ULONG_PTR
)FileRecord
+ sizeof(ULONG
) * 2;
527 ReadAttribute(PDEVICE_EXTENSION Vcb
,
528 PNTFS_ATTR_CONTEXT Context
,
535 LONGLONG DataRunOffset
;
536 ULONGLONG DataRunLength
;
537 LONGLONG DataRunStartLCN
;
538 ULONGLONG CurrentOffset
;
546 if (!Context
->Record
.IsNonResident
)
548 if (Offset
> Context
->Record
.Resident
.ValueLength
)
550 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
551 Length
= (ULONG
)(Context
->Record
.Resident
.ValueLength
- Offset
);
552 RtlCopyMemory(Buffer
, (PCHAR
)&Context
->Record
+ Context
->Record
.Resident
.ValueOffset
+ Offset
, Length
);
557 * Non-resident attribute
561 * I. Find the corresponding start data run.
566 // FIXME: Cache seems to be non-working. Disable it for now
567 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
570 DataRun
= Context
->CacheRun
;
571 LastLCN
= Context
->CacheRunLastLCN
;
572 DataRunStartLCN
= Context
->CacheRunStartLCN
;
573 DataRunLength
= Context
->CacheRunLength
;
574 CurrentOffset
= Context
->CacheRunCurrentOffset
;
579 ULONG UsedBufferSize
;
580 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
585 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
586 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
588 Vcb
->NtfsInfo
.BytesPerFileRecord
,
591 DataRun
= TempBuffer
;
595 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
596 if (DataRunOffset
!= -1)
598 /* Normal data run. */
599 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
600 LastLCN
= DataRunStartLCN
;
604 /* Sparse data run. */
605 DataRunStartLCN
= -1;
608 if (Offset
>= CurrentOffset
&&
609 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
619 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
624 * II. Go through the run list and read the data
627 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
628 if (DataRunStartLCN
== -1)
630 RtlZeroMemory(Buffer
, ReadLength
);
631 Status
= STATUS_SUCCESS
;
635 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
636 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
,
638 Vcb
->NtfsInfo
.BytesPerSector
,
642 if (NT_SUCCESS(Status
))
644 Length
-= ReadLength
;
645 Buffer
+= ReadLength
;
646 AlreadyRead
+= ReadLength
;
648 if (ReadLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
650 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
651 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
652 if (DataRunOffset
!= (ULONGLONG
)-1)
654 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
655 LastLCN
= DataRunStartLCN
;
658 DataRunStartLCN
= -1;
663 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
664 if (DataRunStartLCN
== -1)
665 RtlZeroMemory(Buffer
, ReadLength
);
668 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
669 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
671 Vcb
->NtfsInfo
.BytesPerSector
,
674 if (!NT_SUCCESS(Status
))
678 Length
-= ReadLength
;
679 Buffer
+= ReadLength
;
680 AlreadyRead
+= ReadLength
;
682 /* We finished this request, but there still data in this data run. */
683 if (Length
== 0 && ReadLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
687 * Go to next run in the list.
692 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
693 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
694 if (DataRunOffset
!= -1)
696 /* Normal data run. */
697 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
698 LastLCN
= DataRunStartLCN
;
702 /* Sparse data run. */
703 DataRunStartLCN
= -1;
710 if (Context
->Record
.IsNonResident
)
711 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
713 Context
->CacheRun
= DataRun
;
714 Context
->CacheRunOffset
= Offset
+ AlreadyRead
;
715 Context
->CacheRunStartLCN
= DataRunStartLCN
;
716 Context
->CacheRunLength
= DataRunLength
;
717 Context
->CacheRunLastLCN
= LastLCN
;
718 Context
->CacheRunCurrentOffset
= CurrentOffset
;
725 * @name WriteAttribute
728 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
729 * and it still needs more documentation / cleaning up.
732 * Volume Control Block indicating which volume to write the attribute to
735 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
738 * Offset, in bytes, from the beginning of the attribute indicating where to start
742 * The data that's being written to the device
745 * How much data will be written, in bytes
747 * @param RealLengthWritten
748 * Pointer to a ULONG which will receive how much data was written, in bytes
751 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
752 * writing to a sparse file.
754 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
755 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
760 WriteAttribute(PDEVICE_EXTENSION Vcb
,
761 PNTFS_ATTR_CONTEXT Context
,
765 PULONG RealLengthWritten
)
769 LONGLONG DataRunOffset
;
770 ULONGLONG DataRunLength
;
771 LONGLONG DataRunStartLCN
;
772 ULONGLONG CurrentOffset
;
775 PUCHAR SourceBuffer
= Buffer
;
776 LONGLONG StartingOffset
;
782 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb
, Context
, Offset
, Buffer
, Length
, RealLengthWritten
);
784 *RealLengthWritten
= 0;
786 // is this a resident attribute?
787 if (!Context
->Record
.IsNonResident
)
789 ULONG AttributeOffset
;
790 PNTFS_ATTR_CONTEXT FoundContext
;
791 PFILE_RECORD_HEADER FileRecord
;
793 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
795 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
796 return STATUS_INVALID_PARAMETER
;
799 FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
803 DPRINT1("Error: Couldn't allocate file record!\n");
804 return STATUS_NO_MEMORY
;
807 // read the file record
808 ReadFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
810 // find where to write the attribute data to
811 Status
= FindAttribute(Vcb
, FileRecord
,
812 Context
->Record
.Type
,
813 (PCWSTR
)((PCHAR
)&Context
->Record
+ Context
->Record
.NameOffset
),
814 Context
->Record
.NameLength
,
818 if (!NT_SUCCESS(Status
))
820 DPRINT1("ERROR: Couldn't find matching attribute!\n");
821 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
825 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset
, AttributeOffset
, Context
->Record
.Resident
.ValueLength
);
826 Offset
+= AttributeOffset
+ Context
->Record
.Resident
.ValueOffset
;
828 if (Offset
+ Length
> Vcb
->NtfsInfo
.BytesPerFileRecord
)
830 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
831 ReleaseAttributeContext(FoundContext
);
832 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
833 return STATUS_INVALID_PARAMETER
;
836 // copy the data being written into the file record
837 RtlCopyMemory((PCHAR
)FileRecord
+ Offset
, Buffer
, Length
);
839 Status
= UpdateFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
841 ReleaseAttributeContext(FoundContext
);
842 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
844 if (NT_SUCCESS(Status
))
845 *RealLengthWritten
= Length
;
850 // This is a non-resident attribute.
852 // I. Find the corresponding start data run.
854 // FIXME: Cache seems to be non-working. Disable it for now
855 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
858 DataRun = Context->CacheRun;
859 LastLCN = Context->CacheRunLastLCN;
860 DataRunStartLCN = Context->CacheRunStartLCN;
861 DataRunLength = Context->CacheRunLength;
862 CurrentOffset = Context->CacheRunCurrentOffset;
866 ULONG UsedBufferSize
;
870 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
871 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
873 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
875 Vcb
->NtfsInfo
.BytesPerFileRecord
,
878 DataRun
= TempBuffer
;
882 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
883 if (DataRunOffset
!= -1)
886 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
887 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
888 LastLCN
= DataRunStartLCN
;
892 // Sparse data run. We can't support writing to sparse files yet
893 // (it may require increasing the allocation size).
894 DataRunStartLCN
= -1;
895 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
896 return STATUS_NOT_IMPLEMENTED
;
899 // Have we reached the data run we're trying to write to?
900 if (Offset
>= CurrentOffset
&&
901 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
908 // We reached the last assigned cluster
909 // TODO: assign new clusters to the end of the file.
910 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
911 // [We can reach here by creating a new file record when the MFT isn't large enough]
912 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
913 return STATUS_END_OF_FILE
;
916 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
920 // II. Go through the run list and write the data
922 /* REVIEWME -- As adapted from NtfsReadAttribute():
923 We seem to be making a special case for the first applicable data run, but I'm not sure why.
924 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
926 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
928 StartingOffset
= DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
;
930 // Write the data to the disk
931 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
934 Vcb
->NtfsInfo
.BytesPerSector
,
935 (PVOID
)SourceBuffer
);
937 // Did the write fail?
938 if (!NT_SUCCESS(Status
))
940 Context
->CacheRun
= DataRun
;
941 Context
->CacheRunOffset
= Offset
;
942 Context
->CacheRunStartLCN
= DataRunStartLCN
;
943 Context
->CacheRunLength
= DataRunLength
;
944 Context
->CacheRunLastLCN
= LastLCN
;
945 Context
->CacheRunCurrentOffset
= CurrentOffset
;
950 Length
-= WriteLength
;
951 SourceBuffer
+= WriteLength
;
952 *RealLengthWritten
+= WriteLength
;
954 // Did we write to the end of the data run?
955 if (WriteLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
957 // Advance to the next data run
958 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
959 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
961 if (DataRunOffset
!= (ULONGLONG
)-1)
963 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
964 LastLCN
= DataRunStartLCN
;
967 DataRunStartLCN
= -1;
970 // Do we have more data to write?
973 // Make sure we don't write past the end of the current data run
974 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
976 // Are we dealing with a sparse data run?
977 if (DataRunStartLCN
== -1)
979 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
980 return STATUS_NOT_IMPLEMENTED
;
984 // write the data to the disk
985 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
986 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
988 Vcb
->NtfsInfo
.BytesPerSector
,
989 (PVOID
)SourceBuffer
);
990 if (!NT_SUCCESS(Status
))
994 Length
-= WriteLength
;
995 SourceBuffer
+= WriteLength
;
996 *RealLengthWritten
+= WriteLength
;
998 // We finished this request, but there's still data in this data run.
999 if (Length
== 0 && WriteLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
1002 // Go to next run in the list.
1006 // that was the last run
1009 // Failed sanity check.
1010 DPRINT1("Encountered EOF before expected!\n");
1011 return STATUS_END_OF_FILE
;
1017 // Advance to the next data run
1018 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1019 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1020 if (DataRunOffset
!= -1)
1023 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1024 LastLCN
= DataRunStartLCN
;
1029 DataRunStartLCN
= -1;
1031 } // end while (Length > 0) [more data to write]
1034 if(Context
->Record
.IsNonResident
)
1035 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1037 Context
->CacheRun
= DataRun
;
1038 Context
->CacheRunOffset
= Offset
+ *RealLengthWritten
;
1039 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1040 Context
->CacheRunLength
= DataRunLength
;
1041 Context
->CacheRunLastLCN
= LastLCN
;
1042 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1048 ReadFileRecord(PDEVICE_EXTENSION Vcb
,
1050 PFILE_RECORD_HEADER file
)
1052 ULONGLONG BytesRead
;
1054 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb
, index
, file
);
1056 BytesRead
= ReadAttribute(Vcb
, Vcb
->MFTContext
, index
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (PCHAR
)file
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1057 if (BytesRead
!= Vcb
->NtfsInfo
.BytesPerFileRecord
)
1059 DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1060 return STATUS_PARTIAL_COPY
;
1063 /* Apply update sequence array fixups. */
1064 DPRINT("Sequence number: %u\n", file
->SequenceNumber
);
1065 return FixupUpdateSequenceArray(Vcb
, &file
->Ntfs
);
1070 * Searches a file's parent directory (given the parent's index in the mft)
1071 * for the given file. Upon finding an index entry for that file, updates
1072 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1074 * (Most of this code was copied from NtfsFindMftRecord)
1077 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb
,
1078 ULONGLONG ParentMFTIndex
,
1079 PUNICODE_STRING FileName
,
1081 ULONGLONG NewDataSize
,
1082 ULONGLONG NewAllocationSize
)
1084 PFILE_RECORD_HEADER MftRecord
;
1085 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1086 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1088 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1090 ULONG CurrentEntry
= 0;
1092 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u)\n", Vcb
, ParentMFTIndex
, FileName
, DirSearch
, NewDataSize
, NewAllocationSize
);
1094 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1095 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1097 if (MftRecord
== NULL
)
1099 return STATUS_INSUFFICIENT_RESOURCES
;
1102 Status
= ReadFileRecord(Vcb
, ParentMFTIndex
, MftRecord
);
1103 if (!NT_SUCCESS(Status
))
1105 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1109 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
1110 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
1111 if (!NT_SUCCESS(Status
))
1113 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1117 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
1118 if (IndexRecord
== NULL
)
1120 ReleaseAttributeContext(IndexRootCtx
);
1121 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1122 return STATUS_INSUFFICIENT_RESOURCES
;
1125 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
1126 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
1127 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
1128 // Index root is always resident.
1129 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
1131 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
1133 Status
= UpdateIndexEntryFileNameSize(Vcb
,
1136 IndexRoot
->SizeOfEntry
,
1146 ReleaseAttributeContext(IndexRootCtx
);
1147 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1148 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1154 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1155 * proper index entry.
1156 * (Heavily based on BrowseIndexEntries)
1159 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb
,
1160 PFILE_RECORD_HEADER MftRecord
,
1162 ULONG IndexBlockSize
,
1163 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1164 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1165 PUNICODE_STRING FileName
,
1167 PULONG CurrentEntry
,
1169 ULONGLONG NewDataSize
,
1170 ULONGLONG NewAllocatedSize
)
1174 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1175 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1176 ULONGLONG IndexAllocationSize
;
1177 PINDEX_BUFFER IndexBuffer
;
1179 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
);
1181 // find the index entry responsible for the file we're trying to update
1182 IndexEntry
= FirstEntry
;
1183 while (IndexEntry
< LastEntry
&&
1184 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1186 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > 0x10 &&
1187 *CurrentEntry
>= *StartEntry
&&
1188 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1189 CompareFileName(FileName
, IndexEntry
, DirSearch
))
1191 *StartEntry
= *CurrentEntry
;
1192 IndexEntry
->FileName
.DataSize
= NewDataSize
;
1193 IndexEntry
->FileName
.AllocatedSize
= NewAllocatedSize
;
1194 // indicate that the caller will still need to write the structure to the disk
1195 return STATUS_PENDING
;
1198 (*CurrentEntry
) += 1;
1199 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1200 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1203 /* If we're already browsing a subnode */
1204 if (IndexRecord
== NULL
)
1206 return STATUS_OBJECT_PATH_NOT_FOUND
;
1209 /* If there's no subnode */
1210 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1212 return STATUS_OBJECT_PATH_NOT_FOUND
;
1215 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1216 if (!NT_SUCCESS(Status
))
1218 DPRINT("Corrupted filesystem!\n");
1222 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
1223 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1224 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1226 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1227 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1228 if (!NT_SUCCESS(Status
))
1233 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1234 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1235 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1236 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1237 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1238 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1240 Status
= UpdateIndexEntryFileNameSize(NULL
, NULL
, NULL
, 0, FirstEntry
, LastEntry
, FileName
, StartEntry
, CurrentEntry
, DirSearch
, NewDataSize
, NewAllocatedSize
);
1241 if (Status
== STATUS_PENDING
)
1243 // write the index record back to disk
1246 // first we need to update the fixup values for the index block
1247 Status
= AddFixupArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1248 if (!NT_SUCCESS(Status
))
1250 DPRINT1("Error: Failed to update fixup sequence array!\n");
1254 Status
= WriteAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, (const PUCHAR
)IndexRecord
, IndexBlockSize
, &Written
);
1255 if (!NT_SUCCESS(Status
))
1257 DPRINT1("ERROR Performing write!\n");
1261 Status
= STATUS_SUCCESS
;
1264 if (NT_SUCCESS(Status
))
1270 ReleaseAttributeContext(IndexAllocationCtx
);
1275 * @name UpdateFileRecord
1278 * Writes a file record to the master file table, at a given index.
1281 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1284 * Target index in the master file table to store the file record.
1287 * Pointer to the complete file record which will be written to the master file table.
1290 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1294 UpdateFileRecord(PDEVICE_EXTENSION Vcb
,
1296 PFILE_RECORD_HEADER FileRecord
)
1299 NTSTATUS Status
= STATUS_SUCCESS
;
1301 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb
, MftIndex
, FileRecord
);
1303 // Add the fixup array to prepare the data for writing to disk
1304 AddFixupArray(Vcb
, &FileRecord
->Ntfs
);
1306 // write the file record to the master file table
1307 Status
= WriteAttribute(Vcb
, Vcb
->MFTContext
, MftIndex
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (const PUCHAR
)FileRecord
, Vcb
->NtfsInfo
.BytesPerFileRecord
, &BytesWritten
);
1309 if (!NT_SUCCESS(Status
))
1311 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1314 // remove the fixup array (so the file record pointer can still be used)
1315 FixupUpdateSequenceArray(Vcb
, &FileRecord
->Ntfs
);
1322 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb
,
1323 PNTFS_RECORD_HEADER Record
)
1330 USA
= (USHORT
*)((PCHAR
)Record
+ Record
->UsaOffset
);
1331 USANumber
= *(USA
++);
1332 USACount
= Record
->UsaCount
- 1; /* Exclude the USA Number. */
1333 Block
= (USHORT
*)((PCHAR
)Record
+ Vcb
->NtfsInfo
.BytesPerSector
- 2);
1335 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb
, Record
, USANumber
, USACount
);
1339 if (*Block
!= USANumber
)
1341 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block
, USANumber
);
1342 return STATUS_UNSUCCESSFUL
;
1345 Block
= (USHORT
*)((PCHAR
)Block
+ Vcb
->NtfsInfo
.BytesPerSector
);
1349 return STATUS_SUCCESS
;
1353 * @name AddNewMftEntry
1356 * Adds a file record to the master file table of a given device.
1359 * Pointer to a complete file record which will be saved to disk.
1362 * Pointer to the DEVICE_EXTENSION of the target drive.
1364 * @param DestinationIndex
1365 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1368 * STATUS_SUCCESS on success.
1369 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1370 * to read the attribute.
1371 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1372 * STATUS_NOT_IMPLEMENTED if we need to increase the size of the MFT.
1376 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord
,
1377 PDEVICE_EXTENSION DeviceExt
,
1378 PULONGLONG DestinationIndex
)
1380 NTSTATUS Status
= STATUS_SUCCESS
;
1383 ULONGLONG BitmapDataSize
;
1384 ULONGLONG AttrBytesRead
;
1386 ULONG LengthWritten
;
1388 // First, we have to read the mft's $Bitmap attribute
1389 PNTFS_ATTR_CONTEXT BitmapContext
;
1390 Status
= FindAttribute(DeviceExt
, DeviceExt
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, NULL
);
1391 if (!NT_SUCCESS(Status
))
1393 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1397 // allocate a buffer for the $Bitmap attribute
1398 BitmapDataSize
= AttributeDataLength(&BitmapContext
->Record
);
1399 BitmapData
= ExAllocatePoolWithTag(NonPagedPool
, BitmapDataSize
, TAG_NTFS
);
1402 ReleaseAttributeContext(BitmapContext
);
1403 return STATUS_INSUFFICIENT_RESOURCES
;
1406 // read $Bitmap attribute
1407 AttrBytesRead
= ReadAttribute(DeviceExt
, BitmapContext
, 0, BitmapData
, BitmapDataSize
);
1409 if (AttrBytesRead
== 0)
1411 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1412 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1413 ReleaseAttributeContext(BitmapContext
);
1414 return STATUS_OBJECT_NAME_NOT_FOUND
;
1417 // convert buffer into bitmap
1418 RtlInitializeBitMap(&Bitmap
, (PULONG
)BitmapData
, BitmapDataSize
* 8);
1420 // set next available bit, preferrably after 23rd bit
1421 MftIndex
= RtlFindClearBitsAndSet(&Bitmap
, 1, 24);
1422 if ((LONG
)MftIndex
== -1)
1424 DPRINT1("ERROR: Couldn't find free space in MFT for file record!\n");
1426 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1427 ReleaseAttributeContext(BitmapContext
);
1429 // TODO: increase mft size
1430 return STATUS_NOT_IMPLEMENTED
;
1433 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex
);
1435 // update file record with index
1436 FileRecord
->MFTRecordNumber
= MftIndex
;
1438 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1440 // write the bitmap back to the MFT's $Bitmap attribute
1441 Status
= WriteAttribute(DeviceExt
, BitmapContext
, 0, BitmapData
, BitmapDataSize
, &LengthWritten
);
1442 if (!NT_SUCCESS(Status
))
1444 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1445 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1446 ReleaseAttributeContext(BitmapContext
);
1450 // update the file record (write it to disk)
1451 Status
= UpdateFileRecord(DeviceExt
, MftIndex
, FileRecord
);
1453 if (!NT_SUCCESS(Status
))
1455 DPRINT1("ERROR: Unable to write file record!\n");
1456 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1457 ReleaseAttributeContext(BitmapContext
);
1461 *DestinationIndex
= MftIndex
;
1463 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1464 ReleaseAttributeContext(BitmapContext
);
1470 AddFixupArray(PDEVICE_EXTENSION Vcb
,
1471 PNTFS_RECORD_HEADER Record
)
1473 USHORT
*pShortToFixUp
;
1474 unsigned int ArrayEntryCount
= Record
->UsaCount
- 1;
1475 unsigned int Offset
= Vcb
->NtfsInfo
.BytesPerSector
- 2;
1478 PFIXUP_ARRAY fixupArray
= (PFIXUP_ARRAY
)((UCHAR
*)Record
+ Record
->UsaOffset
);
1480 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb
, Record
, fixupArray
->USN
, ArrayEntryCount
);
1484 for (i
= 0; i
< ArrayEntryCount
; i
++)
1486 DPRINT("USN: %u\tOffset: %u\n", fixupArray
->USN
, Offset
);
1488 pShortToFixUp
= (USHORT
*)((PCHAR
)Record
+ Offset
);
1489 fixupArray
->Array
[i
] = *pShortToFixUp
;
1490 *pShortToFixUp
= fixupArray
->USN
;
1491 Offset
+= Vcb
->NtfsInfo
.BytesPerSector
;
1494 return STATUS_SUCCESS
;
1498 ReadLCN(PDEVICE_EXTENSION Vcb
,
1503 LARGE_INTEGER DiskSector
;
1505 DiskSector
.QuadPart
= lcn
;
1507 return NtfsReadSectors(Vcb
->StorageDevice
,
1508 DiskSector
.u
.LowPart
* Vcb
->NtfsInfo
.SectorsPerCluster
,
1509 count
* Vcb
->NtfsInfo
.SectorsPerCluster
,
1510 Vcb
->NtfsInfo
.BytesPerSector
,
1517 CompareFileName(PUNICODE_STRING FileName
,
1518 PINDEX_ENTRY_ATTRIBUTE IndexEntry
,
1521 BOOLEAN Ret
, Alloc
= FALSE
;
1522 UNICODE_STRING EntryName
;
1524 EntryName
.Buffer
= IndexEntry
->FileName
.Name
;
1526 EntryName
.MaximumLength
= IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
1530 UNICODE_STRING IntFileName
;
1531 if (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
)
1533 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName
, FileName
, TRUE
)));
1538 IntFileName
= *FileName
;
1541 Ret
= FsRtlIsNameInExpression(&IntFileName
, &EntryName
, (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
), NULL
);
1545 RtlFreeUnicodeString(&IntFileName
);
1552 return (RtlCompareUnicodeString(FileName
, &EntryName
, (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
)) == 0);
1559 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry
)
1561 DPRINT1("Entry: %p\n", IndexEntry
);
1562 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry
->Data
.Directory
.IndexedFile
);
1563 DPRINT1("\tLength: %u\n", IndexEntry
->Length
);
1564 DPRINT1("\tKeyLength: %u\n", IndexEntry
->KeyLength
);
1565 DPRINT1("\tFlags: %x\n", IndexEntry
->Flags
);
1566 DPRINT1("\tReserved: %x\n", IndexEntry
->Reserved
);
1567 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry
->FileName
.DirectoryFileReferenceNumber
);
1568 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry
->FileName
.CreationTime
);
1569 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry
->FileName
.ChangeTime
);
1570 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry
->FileName
.LastWriteTime
);
1571 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry
->FileName
.LastAccessTime
);
1572 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry
->FileName
.AllocatedSize
);
1573 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry
->FileName
.DataSize
);
1574 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry
->FileName
.FileAttributes
);
1575 DPRINT1("\t\tNameLength: %u\n", IndexEntry
->FileName
.NameLength
);
1576 DPRINT1("\t\tNameType: %x\n", IndexEntry
->FileName
.NameType
);
1577 DPRINT1("\t\tName: %.*S\n", IndexEntry
->FileName
.NameLength
, IndexEntry
->FileName
.Name
);
1582 BrowseIndexEntries(PDEVICE_EXTENSION Vcb
,
1583 PFILE_RECORD_HEADER MftRecord
,
1585 ULONG IndexBlockSize
,
1586 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1587 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1588 PUNICODE_STRING FileName
,
1590 PULONG CurrentEntry
,
1592 ULONGLONG
*OutMFTIndex
)
1596 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1597 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1598 ULONGLONG IndexAllocationSize
;
1599 PINDEX_BUFFER IndexBuffer
;
1601 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
);
1603 IndexEntry
= FirstEntry
;
1604 while (IndexEntry
< LastEntry
&&
1605 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1607 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > 0x10 &&
1608 *CurrentEntry
>= *StartEntry
&&
1609 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1610 CompareFileName(FileName
, IndexEntry
, DirSearch
))
1612 *StartEntry
= *CurrentEntry
;
1613 *OutMFTIndex
= (IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
);
1614 return STATUS_SUCCESS
;
1617 (*CurrentEntry
) += 1;
1618 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1619 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1622 /* If we're already browsing a subnode */
1623 if (IndexRecord
== NULL
)
1625 return STATUS_OBJECT_PATH_NOT_FOUND
;
1628 /* If there's no subnode */
1629 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1631 return STATUS_OBJECT_PATH_NOT_FOUND
;
1634 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1635 if (!NT_SUCCESS(Status
))
1637 DPRINT("Corrupted filesystem!\n");
1641 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
1642 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1643 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1645 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1646 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1647 if (!NT_SUCCESS(Status
))
1652 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1653 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1654 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1655 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1656 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1657 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1659 Status
= BrowseIndexEntries(NULL
, NULL
, NULL
, 0, FirstEntry
, LastEntry
, FileName
, StartEntry
, CurrentEntry
, DirSearch
, OutMFTIndex
);
1660 if (NT_SUCCESS(Status
))
1666 ReleaseAttributeContext(IndexAllocationCtx
);
1671 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb
,
1673 PUNICODE_STRING FileName
,
1676 ULONGLONG
*OutMFTIndex
)
1678 PFILE_RECORD_HEADER MftRecord
;
1679 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1680 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1682 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1684 ULONG CurrentEntry
= 0;
1686 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb
, MFTIndex
, FileName
, *FirstEntry
, DirSearch
, OutMFTIndex
);
1688 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1689 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1691 if (MftRecord
== NULL
)
1693 return STATUS_INSUFFICIENT_RESOURCES
;
1696 Status
= ReadFileRecord(Vcb
, MFTIndex
, MftRecord
);
1697 if (!NT_SUCCESS(Status
))
1699 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1703 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
1704 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
1705 if (!NT_SUCCESS(Status
))
1707 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1711 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
1712 if (IndexRecord
== NULL
)
1714 ReleaseAttributeContext(IndexRootCtx
);
1715 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1716 return STATUS_INSUFFICIENT_RESOURCES
;
1719 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
1720 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
1721 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
1722 /* Index root is always resident. */
1723 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
1724 ReleaseAttributeContext(IndexRootCtx
);
1726 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
1728 Status
= BrowseIndexEntries(Vcb
, MftRecord
, IndexRecord
, IndexRoot
->SizeOfEntry
, IndexEntry
, IndexEntryEnd
, FileName
, FirstEntry
, &CurrentEntry
, DirSearch
, OutMFTIndex
);
1730 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1731 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1737 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb
,
1738 PUNICODE_STRING PathName
,
1739 PFILE_RECORD_HEADER
*FileRecord
,
1740 PULONGLONG MFTIndex
,
1741 ULONGLONG CurrentMFTIndex
)
1743 UNICODE_STRING Current
, Remaining
;
1745 ULONG FirstEntry
= 0;
1747 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb
, PathName
, FileRecord
, CurrentMFTIndex
);
1749 FsRtlDissectName(*PathName
, &Current
, &Remaining
);
1751 while (Current
.Length
!= 0)
1753 DPRINT("Current: %wZ\n", &Current
);
1755 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, &Current
, &FirstEntry
, FALSE
, &CurrentMFTIndex
);
1756 if (!NT_SUCCESS(Status
))
1761 if (Remaining
.Length
== 0)
1764 FsRtlDissectName(Current
, &Current
, &Remaining
);
1767 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1768 if (*FileRecord
== NULL
)
1770 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
1771 return STATUS_INSUFFICIENT_RESOURCES
;
1774 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
1775 if (!NT_SUCCESS(Status
))
1777 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
1778 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
1782 *MFTIndex
= CurrentMFTIndex
;
1784 return STATUS_SUCCESS
;
1788 NtfsLookupFile(PDEVICE_EXTENSION Vcb
,
1789 PUNICODE_STRING PathName
,
1790 PFILE_RECORD_HEADER
*FileRecord
,
1791 PULONGLONG MFTIndex
)
1793 return NtfsLookupFileAt(Vcb
, PathName
, FileRecord
, MFTIndex
, NTFS_FILE_ROOT
);
1797 * @name NtfsDumpFileRecord
1800 * Provides diagnostic information about a file record. Prints a hex dump
1801 * of the entire record (based on the size reported by FileRecord->ByesInUse),
1802 * then prints a dump of each attribute.
1805 * Pointer to a DEVICE_EXTENSION describing the volume.
1808 * Pointer to the file record to be analyzed.
1811 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
1812 * in size, and not just the header.
1816 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb
,
1817 PFILE_RECORD_HEADER FileRecord
)
1821 // dump binary data, 8 bytes at a time
1822 for (i
= 0; i
< FileRecord
->BytesInUse
; i
+= 8)
1824 // display current offset, in hex
1825 DbgPrint("\t%03x\t", i
);
1827 // display hex value of each of the next 8 bytes
1828 for (j
= 0; j
< 8; j
++)
1829 DbgPrint("%02x ", *(PUCHAR
)((ULONG_PTR
)FileRecord
+ i
+ j
));
1833 NtfsDumpFileAttributes(Vcb
, FileRecord
);
1837 NtfsFindFileAt(PDEVICE_EXTENSION Vcb
,
1838 PUNICODE_STRING SearchPattern
,
1840 PFILE_RECORD_HEADER
*FileRecord
,
1841 PULONGLONG MFTIndex
,
1842 ULONGLONG CurrentMFTIndex
)
1846 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb
, SearchPattern
, *FirstEntry
, FileRecord
, MFTIndex
, CurrentMFTIndex
);
1848 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, SearchPattern
, FirstEntry
, TRUE
, &CurrentMFTIndex
);
1849 if (!NT_SUCCESS(Status
))
1851 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status
);
1855 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1856 if (*FileRecord
== NULL
)
1858 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
1859 return STATUS_INSUFFICIENT_RESOURCES
;
1862 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
1863 if (!NT_SUCCESS(Status
))
1865 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
1866 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
1870 *MFTIndex
= CurrentMFTIndex
;
1872 return STATUS_SUCCESS
;