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
;
53 ULONGLONG NextVBN
= 0;
54 PUCHAR DataRun
= (PUCHAR
)&Context
->Record
+ Context
->Record
.NonResident
.MappingPairsOffset
;
56 Context
->CacheRun
= DataRun
;
57 Context
->CacheRunOffset
= 0;
58 Context
->CacheRun
= DecodeRun(Context
->CacheRun
, &DataRunOffset
, &DataRunLength
);
59 Context
->CacheRunLength
= DataRunLength
;
60 if (DataRunOffset
!= -1)
63 Context
->CacheRunStartLCN
=
64 Context
->CacheRunLastLCN
= DataRunOffset
;
69 Context
->CacheRunStartLCN
= -1;
70 Context
->CacheRunLastLCN
= 0;
72 Context
->CacheRunCurrentOffset
= 0;
74 // Convert the data runs to a map control block
75 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun
, &Context
->DataRunsMCB
, &NextVBN
)))
77 DPRINT1("Unable to convert data runs to MCB!\n");
78 ExFreePoolWithTag(Context
, TAG_NTFS
);
88 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context
)
90 if (Context
->Record
.IsNonResident
)
92 FsRtlUninitializeLargeMcb(&Context
->DataRunsMCB
);
95 ExFreePoolWithTag(Context
, TAG_NTFS
);
100 * @name FindAttribute
103 * Searches a file record for an attribute matching the given type and name.
106 * Optional pointer to a ULONG that will receive the offset of the found attribute
107 * from the beginning of the record. Can be set to NULL.
110 FindAttribute(PDEVICE_EXTENSION Vcb
,
111 PFILE_RECORD_HEADER MftRecord
,
115 PNTFS_ATTR_CONTEXT
* AttrCtx
,
120 FIND_ATTR_CONTXT Context
;
121 PNTFS_ATTR_RECORD Attribute
;
123 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %u, %p)\n", Vcb
, MftRecord
, Type
, Name
, NameLength
, AttrCtx
);
126 Status
= FindFirstAttribute(&Context
, Vcb
, MftRecord
, FALSE
, &Attribute
);
127 while (NT_SUCCESS(Status
))
129 if (Attribute
->Type
== Type
&& Attribute
->NameLength
== NameLength
)
135 AttrName
= (PWCHAR
)((PCHAR
)Attribute
+ Attribute
->NameOffset
);
136 DPRINT("%.*S, %.*S\n", Attribute
->NameLength
, AttrName
, NameLength
, Name
);
137 if (RtlCompareMemory(AttrName
, Name
, NameLength
<< 1) == (NameLength
<< 1))
149 /* Found it, fill up the context and return. */
150 DPRINT("Found context\n");
151 *AttrCtx
= PrepareAttributeContext(Attribute
);
153 (*AttrCtx
)->FileMFTIndex
= MftRecord
->MFTRecordNumber
;
156 *Offset
= Context
.Offset
;
158 FindCloseAttribute(&Context
);
159 return STATUS_SUCCESS
;
163 Status
= FindNextAttribute(&Context
, &Attribute
);
166 FindCloseAttribute(&Context
);
167 return STATUS_OBJECT_NAME_NOT_FOUND
;
172 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord
)
174 if (AttrRecord
->IsNonResident
)
175 return AttrRecord
->NonResident
.AllocatedSize
;
177 return AttrRecord
->Resident
.ValueLength
;
182 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord
)
184 if (AttrRecord
->IsNonResident
)
185 return AttrRecord
->NonResident
.DataSize
;
187 return AttrRecord
->Resident
.ValueLength
;
191 InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext
,
192 PFILE_RECORD_HEADER FileRecord
,
196 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
197 ULONG NextAttributeOffset
;
199 DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext
, FileRecord
, AttrOffset
, DataSize
);
201 // update ValueLength Field
202 AttrContext
->Record
.Resident
.ValueLength
=
203 Destination
->Resident
.ValueLength
= DataSize
;
205 // calculate the record length and end marker offset
206 AttrContext
->Record
.Length
=
207 Destination
->Length
= DataSize
+ AttrContext
->Record
.Resident
.ValueOffset
;
208 NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
210 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
211 if (NextAttributeOffset
% 8 != 0)
213 USHORT Padding
= 8 - (NextAttributeOffset
% 8);
214 NextAttributeOffset
+= Padding
;
215 AttrContext
->Record
.Length
+= Padding
;
216 Destination
->Length
+= Padding
;
219 // advance Destination to the final "attribute" and set the file record end
220 Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Destination
+ Destination
->Length
);
221 SetFileRecordEnd(FileRecord
, Destination
, FILE_RECORD_END
);
225 * @parameter FileRecord
226 * Pointer to a file record. Must be a full record at least
227 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
230 SetAttributeDataLength(PFILE_OBJECT FileObject
,
232 PNTFS_ATTR_CONTEXT AttrContext
,
234 PFILE_RECORD_HEADER FileRecord
,
235 PLARGE_INTEGER DataSize
)
237 NTSTATUS Status
= STATUS_SUCCESS
;
238 ULONG BytesPerCluster
= Fcb
->Vcb
->NtfsInfo
.BytesPerCluster
;
240 // are we truncating the file?
241 if (DataSize
->QuadPart
< AttributeDataLength(&AttrContext
->Record
))
243 if (!MmCanFileBeTruncated(FileObject
->SectionObjectPointer
, DataSize
))
245 DPRINT1("Can't truncate a memory-mapped file!\n");
246 return STATUS_USER_MAPPED_FILE
;
250 if (AttrContext
->Record
.IsNonResident
)
252 ULONGLONG AllocationSize
= ROUND_UP(DataSize
->QuadPart
, BytesPerCluster
);
253 PNTFS_ATTR_RECORD DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
254 ULONG ExistingClusters
= AttrContext
->Record
.NonResident
.AllocatedSize
/ BytesPerCluster
;
256 // do we need to increase the allocation size?
257 if (AttrContext
->Record
.NonResident
.AllocatedSize
< AllocationSize
)
259 ULONG ClustersNeeded
= (AllocationSize
/ BytesPerCluster
) - ExistingClusters
;
260 LARGE_INTEGER LastClusterInDataRun
;
261 ULONG NextAssignedCluster
;
262 ULONG AssignedClusters
;
264 if (ExistingClusters
== 0)
266 LastClusterInDataRun
.QuadPart
= 0;
270 if (!FsRtlLookupLargeMcbEntry(&AttrContext
->DataRunsMCB
,
271 (LONGLONG
)AttrContext
->Record
.NonResident
.HighestVCN
,
272 (PLONGLONG
)&LastClusterInDataRun
.QuadPart
,
278 DPRINT1("Error looking up final large MCB entry!\n");
280 // Most likely, HighestVCN went above the largest mapping
281 DPRINT1("Highest VCN of record: %I64u\n", AttrContext
->Record
.NonResident
.HighestVCN
);
282 return STATUS_INVALID_PARAMETER
;
286 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun
.QuadPart
);
287 DPRINT("Highest VCN of record: %I64u\n", AttrContext
->Record
.NonResident
.HighestVCN
);
289 while (ClustersNeeded
> 0)
291 Status
= NtfsAllocateClusters(Fcb
->Vcb
,
292 LastClusterInDataRun
.LowPart
+ 1,
294 &NextAssignedCluster
,
297 if (!NT_SUCCESS(Status
))
299 DPRINT1("Error: Unable to allocate requested clusters!\n");
303 // now we need to add the clusters we allocated to the data run
304 Status
= AddRun(Fcb
->Vcb
, AttrContext
, AttrOffset
, FileRecord
, NextAssignedCluster
, AssignedClusters
);
305 if (!NT_SUCCESS(Status
))
307 DPRINT1("Error: Unable to add data run!\n");
311 ClustersNeeded
-= AssignedClusters
;
312 LastClusterInDataRun
.LowPart
= NextAssignedCluster
+ AssignedClusters
- 1;
315 else if (AttrContext
->Record
.NonResident
.AllocatedSize
> AllocationSize
)
317 // shrink allocation size
318 ULONG ClustersToFree
= ExistingClusters
- (AllocationSize
/ BytesPerCluster
);
319 Status
= FreeClusters(Fcb
->Vcb
, AttrContext
, AttrOffset
, FileRecord
, ClustersToFree
);
322 // TODO: is the file compressed, encrypted, or sparse?
324 // NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource
326 Fcb
->RFCB
.AllocationSize
.QuadPart
= AllocationSize
;
327 AttrContext
->Record
.NonResident
.AllocatedSize
= AllocationSize
;
328 AttrContext
->Record
.NonResident
.DataSize
= DataSize
->QuadPart
;
329 AttrContext
->Record
.NonResident
.InitializedSize
= DataSize
->QuadPart
;
331 DestinationAttribute
->NonResident
.AllocatedSize
= AllocationSize
;
332 DestinationAttribute
->NonResident
.DataSize
= DataSize
->QuadPart
;
333 DestinationAttribute
->NonResident
.InitializedSize
= DataSize
->QuadPart
;
335 DPRINT("Allocated Size: %I64u\n", DestinationAttribute
->NonResident
.AllocatedSize
);
339 // resident attribute
341 // find the next attribute
342 ULONG NextAttributeOffset
= AttrOffset
+ AttrContext
->Record
.Length
;
343 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((PCHAR
)FileRecord
+ NextAttributeOffset
);
345 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
347 // Do we need to increase the data length?
348 if (DataSize
->QuadPart
> AttrContext
->Record
.Resident
.ValueLength
)
350 // There's usually padding at the end of a record. Do we need to extend past it?
351 ULONG MaxValueLength
= AttrContext
->Record
.Length
- AttrContext
->Record
.Resident
.ValueOffset
;
352 if (MaxValueLength
< DataSize
->LowPart
)
354 // If this is the last attribute, we could move the end marker to the very end of the file record
355 MaxValueLength
+= Fcb
->Vcb
->NtfsInfo
.BytesPerFileRecord
- NextAttributeOffset
- (sizeof(ULONG
) * 2);
357 if (MaxValueLength
< DataSize
->LowPart
|| NextAttribute
->Type
!= AttributeEnd
)
359 // convert attribute to non-resident
360 PNTFS_ATTR_RECORD Destination
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
361 LARGE_INTEGER AttribDataSize
;
363 ULONG EndAttributeOffset
;
366 DPRINT1("Converting attribute to non-resident.\n");
368 AttribDataSize
.QuadPart
= AttrContext
->Record
.Resident
.ValueLength
;
370 // Is there existing data we need to back-up?
371 if (AttribDataSize
.QuadPart
> 0)
373 AttribData
= ExAllocatePoolWithTag(NonPagedPool
, AttribDataSize
.QuadPart
, TAG_NTFS
);
374 if (AttribData
== NULL
)
376 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
377 return STATUS_INSUFFICIENT_RESOURCES
;
380 // read data to temp buffer
381 Status
= ReadAttribute(Fcb
->Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
);
382 if (!NT_SUCCESS(Status
))
384 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
385 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
390 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
392 // Zero out the NonResident structure
393 RtlZeroMemory(&AttrContext
->Record
.NonResident
.LowestVCN
,
394 FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.CompressedSize
) - FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.LowestVCN
));
395 RtlZeroMemory(&Destination
->NonResident
.LowestVCN
,
396 FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.CompressedSize
) - FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.LowestVCN
));
398 // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
399 AttrContext
->Record
.NonResident
.MappingPairsOffset
= Destination
->NonResident
.MappingPairsOffset
= 0x40 + (Destination
->NameLength
* 2);
401 // mark the attribute as non-resident
402 AttrContext
->Record
.IsNonResident
= Destination
->IsNonResident
= 1;
404 // update the end of the file record
405 // calculate position of end markers (1 byte for empty data run)
406 EndAttributeOffset
= AttrOffset
+ AttrContext
->Record
.NonResident
.MappingPairsOffset
+ 1;
407 EndAttributeOffset
= ALIGN_UP_BY(EndAttributeOffset
, 8);
410 Destination
->Length
= EndAttributeOffset
- AttrOffset
;
411 AttrContext
->Record
.Length
= Destination
->Length
;
413 // Update the file record end
414 SetFileRecordEnd(FileRecord
,
415 (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ EndAttributeOffset
),
418 // update file record on disk
419 Status
= UpdateFileRecord(Fcb
->Vcb
, AttrContext
->FileMFTIndex
, FileRecord
);
420 if (!NT_SUCCESS(Status
))
422 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
423 if (AttribDataSize
.QuadPart
> 0)
424 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
428 // Initialize the MCB, potentially catch an exception
430 FsRtlInitializeLargeMcb(&AttrContext
->DataRunsMCB
, NonPagedPool
);
431 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
) {
432 _SEH2_YIELD(return _SEH2_GetExceptionCode());
435 // Now we can treat the attribute as non-resident and enlarge it normally
436 Status
= SetAttributeDataLength(FileObject
, Fcb
, AttrContext
, AttrOffset
, FileRecord
, DataSize
);
437 if (!NT_SUCCESS(Status
))
439 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
440 if(AttribData
!= NULL
)
441 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
445 // restore the back-up attribute, if we made one
446 if (AttribDataSize
.QuadPart
> 0)
448 Status
= WriteAttribute(Fcb
->Vcb
, AttrContext
, 0, AttribData
, AttribDataSize
.QuadPart
, &LengthWritten
);
449 if (!NT_SUCCESS(Status
))
451 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
452 // TODO: Reverse migration so no data is lost
453 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
457 ExFreePoolWithTag(AttribData
, TAG_NTFS
);
462 else if (DataSize
->LowPart
< AttrContext
->Record
.Resident
.ValueLength
)
464 // we need to decrease the length
465 if (NextAttribute
->Type
!= AttributeEnd
)
467 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
468 return STATUS_NOT_IMPLEMENTED
;
472 // set the new length of the resident attribute (if we didn't migrate it)
473 if(!AttrContext
->Record
.IsNonResident
)
474 InternalSetResidentAttributeLength(AttrContext
, FileRecord
, AttrOffset
, DataSize
->LowPart
);
477 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
479 // write the updated file record back to disk
480 Status
= UpdateFileRecord(Fcb
->Vcb
, Fcb
->MFTIndex
, FileRecord
);
482 if (NT_SUCCESS(Status
))
484 Fcb
->RFCB
.FileSize
= *DataSize
;
485 Fcb
->RFCB
.ValidDataLength
= *DataSize
;
486 CcSetFileSizes(FileObject
, (PCC_FILE_SIZES
)&Fcb
->RFCB
.AllocationSize
);
489 return STATUS_SUCCESS
;
493 * @name SetFileRecordEnd
496 * This small function sets a new endpoint for the file record. It set's the final
497 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
500 * Pointer to the file record whose endpoint (length) will be set.
503 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
504 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
507 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
508 * a file record, it preserves the final ULONG that previously ended the record, even though this
509 * value is (to my knowledge) never used. We emulate this behavior.
513 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord
,
514 PNTFS_ATTR_RECORD AttrEnd
,
517 // mark the end of attributes
518 AttrEnd
->Type
= AttributeEnd
;
520 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
521 AttrEnd
->Length
= EndMarker
;
523 // recalculate bytes in use
524 FileRecord
->BytesInUse
= (ULONG_PTR
)AttrEnd
- (ULONG_PTR
)FileRecord
+ sizeof(ULONG
) * 2;
528 ReadAttribute(PDEVICE_EXTENSION Vcb
,
529 PNTFS_ATTR_CONTEXT Context
,
536 LONGLONG DataRunOffset
;
537 ULONGLONG DataRunLength
;
538 LONGLONG DataRunStartLCN
;
539 ULONGLONG CurrentOffset
;
547 if (!Context
->Record
.IsNonResident
)
549 if (Offset
> Context
->Record
.Resident
.ValueLength
)
551 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
552 Length
= (ULONG
)(Context
->Record
.Resident
.ValueLength
- Offset
);
553 RtlCopyMemory(Buffer
, (PCHAR
)&Context
->Record
+ Context
->Record
.Resident
.ValueOffset
+ Offset
, Length
);
558 * Non-resident attribute
562 * I. Find the corresponding start data run.
567 // FIXME: Cache seems to be non-working. Disable it for now
568 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
571 DataRun
= Context
->CacheRun
;
572 LastLCN
= Context
->CacheRunLastLCN
;
573 DataRunStartLCN
= Context
->CacheRunStartLCN
;
574 DataRunLength
= Context
->CacheRunLength
;
575 CurrentOffset
= Context
->CacheRunCurrentOffset
;
580 ULONG UsedBufferSize
;
581 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
586 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
587 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
589 Vcb
->NtfsInfo
.BytesPerFileRecord
,
592 DataRun
= TempBuffer
;
596 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
597 if (DataRunOffset
!= -1)
599 /* Normal data run. */
600 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
601 LastLCN
= DataRunStartLCN
;
605 /* Sparse data run. */
606 DataRunStartLCN
= -1;
609 if (Offset
>= CurrentOffset
&&
610 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
620 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
625 * II. Go through the run list and read the data
628 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
629 if (DataRunStartLCN
== -1)
631 RtlZeroMemory(Buffer
, ReadLength
);
632 Status
= STATUS_SUCCESS
;
636 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
637 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
,
639 Vcb
->NtfsInfo
.BytesPerSector
,
643 if (NT_SUCCESS(Status
))
645 Length
-= ReadLength
;
646 Buffer
+= ReadLength
;
647 AlreadyRead
+= ReadLength
;
649 if (ReadLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
651 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
652 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
653 if (DataRunOffset
!= (ULONGLONG
)-1)
655 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
656 LastLCN
= DataRunStartLCN
;
659 DataRunStartLCN
= -1;
664 ReadLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
665 if (DataRunStartLCN
== -1)
666 RtlZeroMemory(Buffer
, ReadLength
);
669 Status
= NtfsReadDisk(Vcb
->StorageDevice
,
670 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
672 Vcb
->NtfsInfo
.BytesPerSector
,
675 if (!NT_SUCCESS(Status
))
679 Length
-= ReadLength
;
680 Buffer
+= ReadLength
;
681 AlreadyRead
+= ReadLength
;
683 /* We finished this request, but there still data in this data run. */
684 if (Length
== 0 && ReadLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
688 * Go to next run in the list.
693 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
694 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
695 if (DataRunOffset
!= -1)
697 /* Normal data run. */
698 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
699 LastLCN
= DataRunStartLCN
;
703 /* Sparse data run. */
704 DataRunStartLCN
= -1;
711 if (Context
->Record
.IsNonResident
)
712 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
714 Context
->CacheRun
= DataRun
;
715 Context
->CacheRunOffset
= Offset
+ AlreadyRead
;
716 Context
->CacheRunStartLCN
= DataRunStartLCN
;
717 Context
->CacheRunLength
= DataRunLength
;
718 Context
->CacheRunLastLCN
= LastLCN
;
719 Context
->CacheRunCurrentOffset
= CurrentOffset
;
726 * @name WriteAttribute
729 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
730 * and it still needs more documentation / cleaning up.
733 * Volume Control Block indicating which volume to write the attribute to
736 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
739 * Offset, in bytes, from the beginning of the attribute indicating where to start
743 * The data that's being written to the device
746 * How much data will be written, in bytes
748 * @param RealLengthWritten
749 * Pointer to a ULONG which will receive how much data was written, in bytes
752 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
753 * writing to a sparse file.
755 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
756 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
761 WriteAttribute(PDEVICE_EXTENSION Vcb
,
762 PNTFS_ATTR_CONTEXT Context
,
766 PULONG RealLengthWritten
)
770 LONGLONG DataRunOffset
;
771 ULONGLONG DataRunLength
;
772 LONGLONG DataRunStartLCN
;
773 ULONGLONG CurrentOffset
;
776 PUCHAR SourceBuffer
= Buffer
;
777 LONGLONG StartingOffset
;
783 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb
, Context
, Offset
, Buffer
, Length
, RealLengthWritten
);
785 *RealLengthWritten
= 0;
787 // is this a resident attribute?
788 if (!Context
->Record
.IsNonResident
)
790 ULONG AttributeOffset
;
791 PNTFS_ATTR_CONTEXT FoundContext
;
792 PFILE_RECORD_HEADER FileRecord
;
794 if (Offset
+ Length
> Context
->Record
.Resident
.ValueLength
)
796 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
797 return STATUS_INVALID_PARAMETER
;
800 FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
804 DPRINT1("Error: Couldn't allocate file record!\n");
805 return STATUS_NO_MEMORY
;
808 // read the file record
809 ReadFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
811 // find where to write the attribute data to
812 Status
= FindAttribute(Vcb
, FileRecord
,
813 Context
->Record
.Type
,
814 (PCWSTR
)((PCHAR
)&Context
->Record
+ Context
->Record
.NameOffset
),
815 Context
->Record
.NameLength
,
819 if (!NT_SUCCESS(Status
))
821 DPRINT1("ERROR: Couldn't find matching attribute!\n");
822 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
826 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset
, AttributeOffset
, Context
->Record
.Resident
.ValueLength
);
827 Offset
+= AttributeOffset
+ Context
->Record
.Resident
.ValueOffset
;
829 if (Offset
+ Length
> Vcb
->NtfsInfo
.BytesPerFileRecord
)
831 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
832 ReleaseAttributeContext(FoundContext
);
833 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
834 return STATUS_INVALID_PARAMETER
;
837 // copy the data being written into the file record
838 RtlCopyMemory((PCHAR
)FileRecord
+ Offset
, Buffer
, Length
);
840 Status
= UpdateFileRecord(Vcb
, Context
->FileMFTIndex
, FileRecord
);
842 ReleaseAttributeContext(FoundContext
);
843 ExFreePoolWithTag(FileRecord
, TAG_NTFS
);
845 if (NT_SUCCESS(Status
))
846 *RealLengthWritten
= Length
;
851 // This is a non-resident attribute.
853 // I. Find the corresponding start data run.
855 // FIXME: Cache seems to be non-working. Disable it for now
856 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
859 DataRun = Context->CacheRun;
860 LastLCN = Context->CacheRunLastLCN;
861 DataRunStartLCN = Context->CacheRunStartLCN;
862 DataRunLength = Context->CacheRunLength;
863 CurrentOffset = Context->CacheRunCurrentOffset;
867 ULONG UsedBufferSize
;
871 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
872 TempBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
874 ConvertLargeMCBToDataRuns(&Context
->DataRunsMCB
,
876 Vcb
->NtfsInfo
.BytesPerFileRecord
,
879 DataRun
= TempBuffer
;
883 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
884 if (DataRunOffset
!= -1)
887 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
888 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
889 LastLCN
= DataRunStartLCN
;
893 // Sparse data run. We can't support writing to sparse files yet
894 // (it may require increasing the allocation size).
895 DataRunStartLCN
= -1;
896 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
897 return STATUS_NOT_IMPLEMENTED
;
900 // Have we reached the data run we're trying to write to?
901 if (Offset
>= CurrentOffset
&&
902 Offset
< CurrentOffset
+ (DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
))
909 // We reached the last assigned cluster
910 // TODO: assign new clusters to the end of the file.
911 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
912 // [We can reach here by creating a new file record when the MFT isn't large enough]
913 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
914 return STATUS_END_OF_FILE
;
917 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
921 // II. Go through the run list and write the data
923 /* REVIEWME -- As adapted from NtfsReadAttribute():
924 We seem to be making a special case for the first applicable data run, but I'm not sure why.
925 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
927 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
), Length
);
929 StartingOffset
= DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
+ Offset
- CurrentOffset
;
931 // Write the data to the disk
932 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
935 Vcb
->NtfsInfo
.BytesPerSector
,
936 (PVOID
)SourceBuffer
);
938 // Did the write fail?
939 if (!NT_SUCCESS(Status
))
941 Context
->CacheRun
= DataRun
;
942 Context
->CacheRunOffset
= Offset
;
943 Context
->CacheRunStartLCN
= DataRunStartLCN
;
944 Context
->CacheRunLength
= DataRunLength
;
945 Context
->CacheRunLastLCN
= LastLCN
;
946 Context
->CacheRunCurrentOffset
= CurrentOffset
;
951 Length
-= WriteLength
;
952 SourceBuffer
+= WriteLength
;
953 *RealLengthWritten
+= WriteLength
;
955 // Did we write to the end of the data run?
956 if (WriteLength
== DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
- (Offset
- CurrentOffset
))
958 // Advance to the next data run
959 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
960 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
962 if (DataRunOffset
!= (ULONGLONG
)-1)
964 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
965 LastLCN
= DataRunStartLCN
;
968 DataRunStartLCN
= -1;
971 // Do we have more data to write?
974 // Make sure we don't write past the end of the current data run
975 WriteLength
= (ULONG
)min(DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
, Length
);
977 // Are we dealing with a sparse data run?
978 if (DataRunStartLCN
== -1)
980 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
981 return STATUS_NOT_IMPLEMENTED
;
985 // write the data to the disk
986 Status
= NtfsWriteDisk(Vcb
->StorageDevice
,
987 DataRunStartLCN
* Vcb
->NtfsInfo
.BytesPerCluster
,
989 Vcb
->NtfsInfo
.BytesPerSector
,
990 (PVOID
)SourceBuffer
);
991 if (!NT_SUCCESS(Status
))
995 Length
-= WriteLength
;
996 SourceBuffer
+= WriteLength
;
997 *RealLengthWritten
+= WriteLength
;
999 // We finished this request, but there's still data in this data run.
1000 if (Length
== 0 && WriteLength
!= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
)
1003 // Go to next run in the list.
1007 // that was the last run
1010 // Failed sanity check.
1011 DPRINT1("Encountered EOF before expected!\n");
1012 return STATUS_END_OF_FILE
;
1018 // Advance to the next data run
1019 CurrentOffset
+= DataRunLength
* Vcb
->NtfsInfo
.BytesPerCluster
;
1020 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1021 if (DataRunOffset
!= -1)
1024 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1025 LastLCN
= DataRunStartLCN
;
1030 DataRunStartLCN
= -1;
1032 } // end while (Length > 0) [more data to write]
1035 if(Context
->Record
.IsNonResident
)
1036 ExFreePoolWithTag(TempBuffer
, TAG_NTFS
);
1038 Context
->CacheRun
= DataRun
;
1039 Context
->CacheRunOffset
= Offset
+ *RealLengthWritten
;
1040 Context
->CacheRunStartLCN
= DataRunStartLCN
;
1041 Context
->CacheRunLength
= DataRunLength
;
1042 Context
->CacheRunLastLCN
= LastLCN
;
1043 Context
->CacheRunCurrentOffset
= CurrentOffset
;
1049 ReadFileRecord(PDEVICE_EXTENSION Vcb
,
1051 PFILE_RECORD_HEADER file
)
1053 ULONGLONG BytesRead
;
1055 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb
, index
, file
);
1057 BytesRead
= ReadAttribute(Vcb
, Vcb
->MFTContext
, index
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (PCHAR
)file
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1058 if (BytesRead
!= Vcb
->NtfsInfo
.BytesPerFileRecord
)
1060 DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1061 return STATUS_PARTIAL_COPY
;
1064 /* Apply update sequence array fixups. */
1065 DPRINT("Sequence number: %u\n", file
->SequenceNumber
);
1066 return FixupUpdateSequenceArray(Vcb
, &file
->Ntfs
);
1071 * Searches a file's parent directory (given the parent's index in the mft)
1072 * for the given file. Upon finding an index entry for that file, updates
1073 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1075 * (Most of this code was copied from NtfsFindMftRecord)
1078 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb
,
1079 ULONGLONG ParentMFTIndex
,
1080 PUNICODE_STRING FileName
,
1082 ULONGLONG NewDataSize
,
1083 ULONGLONG NewAllocationSize
)
1085 PFILE_RECORD_HEADER MftRecord
;
1086 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1087 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1089 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1091 ULONG CurrentEntry
= 0;
1093 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u)\n", Vcb
, ParentMFTIndex
, FileName
, DirSearch
, NewDataSize
, NewAllocationSize
);
1095 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1096 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1098 if (MftRecord
== NULL
)
1100 return STATUS_INSUFFICIENT_RESOURCES
;
1103 Status
= ReadFileRecord(Vcb
, ParentMFTIndex
, MftRecord
);
1104 if (!NT_SUCCESS(Status
))
1106 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1110 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
1111 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
1112 if (!NT_SUCCESS(Status
))
1114 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1118 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
1119 if (IndexRecord
== NULL
)
1121 ReleaseAttributeContext(IndexRootCtx
);
1122 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1123 return STATUS_INSUFFICIENT_RESOURCES
;
1126 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
1127 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
1128 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
1129 // Index root is always resident.
1130 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
1132 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
1134 Status
= UpdateIndexEntryFileNameSize(Vcb
,
1137 IndexRoot
->SizeOfEntry
,
1147 ReleaseAttributeContext(IndexRootCtx
);
1148 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1149 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1155 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1156 * proper index entry.
1157 * (Heavily based on BrowseIndexEntries)
1160 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb
,
1161 PFILE_RECORD_HEADER MftRecord
,
1163 ULONG IndexBlockSize
,
1164 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1165 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1166 PUNICODE_STRING FileName
,
1168 PULONG CurrentEntry
,
1170 ULONGLONG NewDataSize
,
1171 ULONGLONG NewAllocatedSize
)
1175 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1176 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1177 ULONGLONG IndexAllocationSize
;
1178 PINDEX_BUFFER IndexBuffer
;
1180 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
);
1182 // find the index entry responsible for the file we're trying to update
1183 IndexEntry
= FirstEntry
;
1184 while (IndexEntry
< LastEntry
&&
1185 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1187 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > 0x10 &&
1188 *CurrentEntry
>= *StartEntry
&&
1189 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1190 CompareFileName(FileName
, IndexEntry
, DirSearch
))
1192 *StartEntry
= *CurrentEntry
;
1193 IndexEntry
->FileName
.DataSize
= NewDataSize
;
1194 IndexEntry
->FileName
.AllocatedSize
= NewAllocatedSize
;
1195 // indicate that the caller will still need to write the structure to the disk
1196 return STATUS_PENDING
;
1199 (*CurrentEntry
) += 1;
1200 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1201 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1204 /* If we're already browsing a subnode */
1205 if (IndexRecord
== NULL
)
1207 return STATUS_OBJECT_PATH_NOT_FOUND
;
1210 /* If there's no subnode */
1211 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1213 return STATUS_OBJECT_PATH_NOT_FOUND
;
1216 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1217 if (!NT_SUCCESS(Status
))
1219 DPRINT("Corrupted filesystem!\n");
1223 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
1224 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1225 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1227 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1228 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1229 if (!NT_SUCCESS(Status
))
1234 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1235 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1236 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1237 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1238 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1239 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1241 Status
= UpdateIndexEntryFileNameSize(NULL
, NULL
, NULL
, 0, FirstEntry
, LastEntry
, FileName
, StartEntry
, CurrentEntry
, DirSearch
, NewDataSize
, NewAllocatedSize
);
1242 if (Status
== STATUS_PENDING
)
1244 // write the index record back to disk
1247 // first we need to update the fixup values for the index block
1248 Status
= AddFixupArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1249 if (!NT_SUCCESS(Status
))
1251 DPRINT1("Error: Failed to update fixup sequence array!\n");
1255 Status
= WriteAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, (const PUCHAR
)IndexRecord
, IndexBlockSize
, &Written
);
1256 if (!NT_SUCCESS(Status
))
1258 DPRINT1("ERROR Performing write!\n");
1262 Status
= STATUS_SUCCESS
;
1265 if (NT_SUCCESS(Status
))
1271 ReleaseAttributeContext(IndexAllocationCtx
);
1276 * @name UpdateFileRecord
1279 * Writes a file record to the master file table, at a given index.
1282 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1285 * Target index in the master file table to store the file record.
1288 * Pointer to the complete file record which will be written to the master file table.
1291 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1295 UpdateFileRecord(PDEVICE_EXTENSION Vcb
,
1297 PFILE_RECORD_HEADER FileRecord
)
1300 NTSTATUS Status
= STATUS_SUCCESS
;
1302 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb
, MftIndex
, FileRecord
);
1304 // Add the fixup array to prepare the data for writing to disk
1305 AddFixupArray(Vcb
, &FileRecord
->Ntfs
);
1307 // write the file record to the master file table
1308 Status
= WriteAttribute(Vcb
, Vcb
->MFTContext
, MftIndex
* Vcb
->NtfsInfo
.BytesPerFileRecord
, (const PUCHAR
)FileRecord
, Vcb
->NtfsInfo
.BytesPerFileRecord
, &BytesWritten
);
1310 if (!NT_SUCCESS(Status
))
1312 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten
, Vcb
->NtfsInfo
.BytesPerFileRecord
);
1315 // remove the fixup array (so the file record pointer can still be used)
1316 FixupUpdateSequenceArray(Vcb
, &FileRecord
->Ntfs
);
1323 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb
,
1324 PNTFS_RECORD_HEADER Record
)
1331 USA
= (USHORT
*)((PCHAR
)Record
+ Record
->UsaOffset
);
1332 USANumber
= *(USA
++);
1333 USACount
= Record
->UsaCount
- 1; /* Exclude the USA Number. */
1334 Block
= (USHORT
*)((PCHAR
)Record
+ Vcb
->NtfsInfo
.BytesPerSector
- 2);
1336 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb
, Record
, USANumber
, USACount
);
1340 if (*Block
!= USANumber
)
1342 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block
, USANumber
);
1343 return STATUS_UNSUCCESSFUL
;
1346 Block
= (USHORT
*)((PCHAR
)Block
+ Vcb
->NtfsInfo
.BytesPerSector
);
1350 return STATUS_SUCCESS
;
1354 * @name AddNewMftEntry
1357 * Adds a file record to the master file table of a given device.
1360 * Pointer to a complete file record which will be saved to disk.
1363 * Pointer to the DEVICE_EXTENSION of the target drive.
1366 * STATUS_SUCCESS on success.
1367 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1368 * to read the attribute.
1369 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1370 * STATUS_NOT_IMPLEMENTED if we need to increase the size of the MFT.
1374 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord
,
1375 PDEVICE_EXTENSION DeviceExt
)
1377 NTSTATUS Status
= STATUS_SUCCESS
;
1380 ULONGLONG BitmapDataSize
;
1381 ULONGLONG AttrBytesRead
;
1383 ULONG LengthWritten
;
1385 // First, we have to read the mft's $Bitmap attribute
1386 PNTFS_ATTR_CONTEXT BitmapContext
;
1387 Status
= FindAttribute(DeviceExt
, DeviceExt
->MasterFileTable
, AttributeBitmap
, L
"", 0, &BitmapContext
, NULL
);
1388 if (!NT_SUCCESS(Status
))
1390 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1394 // allocate a buffer for the $Bitmap attribute
1395 BitmapDataSize
= AttributeDataLength(&BitmapContext
->Record
);
1396 BitmapData
= ExAllocatePoolWithTag(NonPagedPool
, BitmapDataSize
, TAG_NTFS
);
1399 ReleaseAttributeContext(BitmapContext
);
1400 return STATUS_INSUFFICIENT_RESOURCES
;
1403 // read $Bitmap attribute
1404 AttrBytesRead
= ReadAttribute(DeviceExt
, BitmapContext
, 0, BitmapData
, BitmapDataSize
);
1406 if (AttrBytesRead
== 0)
1408 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1409 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1410 ReleaseAttributeContext(BitmapContext
);
1411 return STATUS_OBJECT_NAME_NOT_FOUND
;
1414 // convert buffer into bitmap
1415 RtlInitializeBitMap(&Bitmap
, (PULONG
)BitmapData
, BitmapDataSize
* 8);
1417 // set next available bit, preferrably after 23rd bit
1418 MftIndex
= RtlFindClearBitsAndSet(&Bitmap
, 1, 24);
1419 if ((LONG
)MftIndex
== -1)
1421 DPRINT1("ERROR: Couldn't find free space in MFT for file record!\n");
1423 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1424 ReleaseAttributeContext(BitmapContext
);
1426 // TODO: increase mft size
1427 return STATUS_NOT_IMPLEMENTED
;
1430 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex
);
1432 // update file record with index
1433 FileRecord
->MFTRecordNumber
= MftIndex
;
1435 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1437 // write the bitmap back to the MFT's $Bitmap attribute
1438 Status
= WriteAttribute(DeviceExt
, BitmapContext
, 0, BitmapData
, BitmapDataSize
, &LengthWritten
);
1439 if (!NT_SUCCESS(Status
))
1441 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1442 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1443 ReleaseAttributeContext(BitmapContext
);
1447 // update the file record (write it to disk)
1448 Status
= UpdateFileRecord(DeviceExt
, MftIndex
, FileRecord
);
1450 if (!NT_SUCCESS(Status
))
1452 DPRINT1("ERROR: Unable to write file record!\n");
1453 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1454 ReleaseAttributeContext(BitmapContext
);
1458 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1459 ReleaseAttributeContext(BitmapContext
);
1465 AddFixupArray(PDEVICE_EXTENSION Vcb
,
1466 PNTFS_RECORD_HEADER Record
)
1468 USHORT
*pShortToFixUp
;
1469 unsigned int ArrayEntryCount
= Record
->UsaCount
- 1;
1470 unsigned int Offset
= Vcb
->NtfsInfo
.BytesPerSector
- 2;
1473 PFIXUP_ARRAY fixupArray
= (PFIXUP_ARRAY
)((UCHAR
*)Record
+ Record
->UsaOffset
);
1475 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb
, Record
, fixupArray
->USN
, ArrayEntryCount
);
1479 for (i
= 0; i
< ArrayEntryCount
; i
++)
1481 DPRINT("USN: %u\tOffset: %u\n", fixupArray
->USN
, Offset
);
1483 pShortToFixUp
= (USHORT
*)((PCHAR
)Record
+ Offset
);
1484 fixupArray
->Array
[i
] = *pShortToFixUp
;
1485 *pShortToFixUp
= fixupArray
->USN
;
1486 Offset
+= Vcb
->NtfsInfo
.BytesPerSector
;
1489 return STATUS_SUCCESS
;
1493 ReadLCN(PDEVICE_EXTENSION Vcb
,
1498 LARGE_INTEGER DiskSector
;
1500 DiskSector
.QuadPart
= lcn
;
1502 return NtfsReadSectors(Vcb
->StorageDevice
,
1503 DiskSector
.u
.LowPart
* Vcb
->NtfsInfo
.SectorsPerCluster
,
1504 count
* Vcb
->NtfsInfo
.SectorsPerCluster
,
1505 Vcb
->NtfsInfo
.BytesPerSector
,
1512 CompareFileName(PUNICODE_STRING FileName
,
1513 PINDEX_ENTRY_ATTRIBUTE IndexEntry
,
1516 BOOLEAN Ret
, Alloc
= FALSE
;
1517 UNICODE_STRING EntryName
;
1519 EntryName
.Buffer
= IndexEntry
->FileName
.Name
;
1521 EntryName
.MaximumLength
= IndexEntry
->FileName
.NameLength
* sizeof(WCHAR
);
1525 UNICODE_STRING IntFileName
;
1526 if (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
)
1528 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName
, FileName
, TRUE
)));
1533 IntFileName
= *FileName
;
1536 Ret
= FsRtlIsNameInExpression(&IntFileName
, &EntryName
, (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
), NULL
);
1540 RtlFreeUnicodeString(&IntFileName
);
1547 return (RtlCompareUnicodeString(FileName
, &EntryName
, (IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_POSIX
)) == 0);
1554 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry
)
1556 DPRINT1("Entry: %p\n", IndexEntry
);
1557 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry
->Data
.Directory
.IndexedFile
);
1558 DPRINT1("\tLength: %u\n", IndexEntry
->Length
);
1559 DPRINT1("\tKeyLength: %u\n", IndexEntry
->KeyLength
);
1560 DPRINT1("\tFlags: %x\n", IndexEntry
->Flags
);
1561 DPRINT1("\tReserved: %x\n", IndexEntry
->Reserved
);
1562 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry
->FileName
.DirectoryFileReferenceNumber
);
1563 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry
->FileName
.CreationTime
);
1564 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry
->FileName
.ChangeTime
);
1565 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry
->FileName
.LastWriteTime
);
1566 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry
->FileName
.LastAccessTime
);
1567 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry
->FileName
.AllocatedSize
);
1568 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry
->FileName
.DataSize
);
1569 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry
->FileName
.FileAttributes
);
1570 DPRINT1("\t\tNameLength: %u\n", IndexEntry
->FileName
.NameLength
);
1571 DPRINT1("\t\tNameType: %x\n", IndexEntry
->FileName
.NameType
);
1572 DPRINT1("\t\tName: %.*S\n", IndexEntry
->FileName
.NameLength
, IndexEntry
->FileName
.Name
);
1577 BrowseIndexEntries(PDEVICE_EXTENSION Vcb
,
1578 PFILE_RECORD_HEADER MftRecord
,
1580 ULONG IndexBlockSize
,
1581 PINDEX_ENTRY_ATTRIBUTE FirstEntry
,
1582 PINDEX_ENTRY_ATTRIBUTE LastEntry
,
1583 PUNICODE_STRING FileName
,
1585 PULONG CurrentEntry
,
1587 ULONGLONG
*OutMFTIndex
)
1591 PINDEX_ENTRY_ATTRIBUTE IndexEntry
;
1592 PNTFS_ATTR_CONTEXT IndexAllocationCtx
;
1593 ULONGLONG IndexAllocationSize
;
1594 PINDEX_BUFFER IndexBuffer
;
1596 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
);
1598 IndexEntry
= FirstEntry
;
1599 while (IndexEntry
< LastEntry
&&
1600 !(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_END
))
1602 if ((IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
) > 0x10 &&
1603 *CurrentEntry
>= *StartEntry
&&
1604 IndexEntry
->FileName
.NameType
!= NTFS_FILE_NAME_DOS
&&
1605 CompareFileName(FileName
, IndexEntry
, DirSearch
))
1607 *StartEntry
= *CurrentEntry
;
1608 *OutMFTIndex
= (IndexEntry
->Data
.Directory
.IndexedFile
& NTFS_MFT_MASK
);
1609 return STATUS_SUCCESS
;
1612 (*CurrentEntry
) += 1;
1613 ASSERT(IndexEntry
->Length
>= sizeof(INDEX_ENTRY_ATTRIBUTE
));
1614 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)IndexEntry
+ IndexEntry
->Length
);
1617 /* If we're already browsing a subnode */
1618 if (IndexRecord
== NULL
)
1620 return STATUS_OBJECT_PATH_NOT_FOUND
;
1623 /* If there's no subnode */
1624 if (!(IndexEntry
->Flags
& NTFS_INDEX_ENTRY_NODE
))
1626 return STATUS_OBJECT_PATH_NOT_FOUND
;
1629 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexAllocation
, L
"$I30", 4, &IndexAllocationCtx
, NULL
);
1630 if (!NT_SUCCESS(Status
))
1632 DPRINT("Corrupted filesystem!\n");
1636 IndexAllocationSize
= AttributeDataLength(&IndexAllocationCtx
->Record
);
1637 Status
= STATUS_OBJECT_PATH_NOT_FOUND
;
1638 for (RecordOffset
= 0; RecordOffset
< IndexAllocationSize
; RecordOffset
+= IndexBlockSize
)
1640 ReadAttribute(Vcb
, IndexAllocationCtx
, RecordOffset
, IndexRecord
, IndexBlockSize
);
1641 Status
= FixupUpdateSequenceArray(Vcb
, &((PFILE_RECORD_HEADER
)IndexRecord
)->Ntfs
);
1642 if (!NT_SUCCESS(Status
))
1647 IndexBuffer
= (PINDEX_BUFFER
)IndexRecord
;
1648 ASSERT(IndexBuffer
->Ntfs
.Type
== NRH_INDX_TYPE
);
1649 ASSERT(IndexBuffer
->Header
.AllocatedSize
+ FIELD_OFFSET(INDEX_BUFFER
, Header
) == IndexBlockSize
);
1650 FirstEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.FirstEntryOffset
);
1651 LastEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)&IndexBuffer
->Header
+ IndexBuffer
->Header
.TotalSizeOfEntries
);
1652 ASSERT(LastEntry
<= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexBuffer
+ IndexBlockSize
));
1654 Status
= BrowseIndexEntries(NULL
, NULL
, NULL
, 0, FirstEntry
, LastEntry
, FileName
, StartEntry
, CurrentEntry
, DirSearch
, OutMFTIndex
);
1655 if (NT_SUCCESS(Status
))
1661 ReleaseAttributeContext(IndexAllocationCtx
);
1666 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb
,
1668 PUNICODE_STRING FileName
,
1671 ULONGLONG
*OutMFTIndex
)
1673 PFILE_RECORD_HEADER MftRecord
;
1674 PNTFS_ATTR_CONTEXT IndexRootCtx
;
1675 PINDEX_ROOT_ATTRIBUTE IndexRoot
;
1677 PINDEX_ENTRY_ATTRIBUTE IndexEntry
, IndexEntryEnd
;
1679 ULONG CurrentEntry
= 0;
1681 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb
, MFTIndex
, FileName
, *FirstEntry
, DirSearch
, OutMFTIndex
);
1683 MftRecord
= ExAllocatePoolWithTag(NonPagedPool
,
1684 Vcb
->NtfsInfo
.BytesPerFileRecord
,
1686 if (MftRecord
== NULL
)
1688 return STATUS_INSUFFICIENT_RESOURCES
;
1691 Status
= ReadFileRecord(Vcb
, MFTIndex
, MftRecord
);
1692 if (!NT_SUCCESS(Status
))
1694 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1698 ASSERT(MftRecord
->Ntfs
.Type
== NRH_FILE_TYPE
);
1699 Status
= FindAttribute(Vcb
, MftRecord
, AttributeIndexRoot
, L
"$I30", 4, &IndexRootCtx
, NULL
);
1700 if (!NT_SUCCESS(Status
))
1702 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1706 IndexRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerIndexRecord
, TAG_NTFS
);
1707 if (IndexRecord
== NULL
)
1709 ReleaseAttributeContext(IndexRootCtx
);
1710 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1711 return STATUS_INSUFFICIENT_RESOURCES
;
1714 ReadAttribute(Vcb
, IndexRootCtx
, 0, IndexRecord
, Vcb
->NtfsInfo
.BytesPerIndexRecord
);
1715 IndexRoot
= (PINDEX_ROOT_ATTRIBUTE
)IndexRecord
;
1716 IndexEntry
= (PINDEX_ENTRY_ATTRIBUTE
)((PCHAR
)&IndexRoot
->Header
+ IndexRoot
->Header
.FirstEntryOffset
);
1717 /* Index root is always resident. */
1718 IndexEntryEnd
= (PINDEX_ENTRY_ATTRIBUTE
)(IndexRecord
+ IndexRoot
->Header
.TotalSizeOfEntries
);
1719 ReleaseAttributeContext(IndexRootCtx
);
1721 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb
->NtfsInfo
.BytesPerIndexRecord
, IndexRoot
->SizeOfEntry
);
1723 Status
= BrowseIndexEntries(Vcb
, MftRecord
, IndexRecord
, IndexRoot
->SizeOfEntry
, IndexEntry
, IndexEntryEnd
, FileName
, FirstEntry
, &CurrentEntry
, DirSearch
, OutMFTIndex
);
1725 ExFreePoolWithTag(IndexRecord
, TAG_NTFS
);
1726 ExFreePoolWithTag(MftRecord
, TAG_NTFS
);
1732 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb
,
1733 PUNICODE_STRING PathName
,
1734 PFILE_RECORD_HEADER
*FileRecord
,
1735 PULONGLONG MFTIndex
,
1736 ULONGLONG CurrentMFTIndex
)
1738 UNICODE_STRING Current
, Remaining
;
1740 ULONG FirstEntry
= 0;
1742 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb
, PathName
, FileRecord
, CurrentMFTIndex
);
1744 FsRtlDissectName(*PathName
, &Current
, &Remaining
);
1746 while (Current
.Length
!= 0)
1748 DPRINT("Current: %wZ\n", &Current
);
1750 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, &Current
, &FirstEntry
, FALSE
, &CurrentMFTIndex
);
1751 if (!NT_SUCCESS(Status
))
1756 if (Remaining
.Length
== 0)
1759 FsRtlDissectName(Current
, &Current
, &Remaining
);
1762 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1763 if (*FileRecord
== NULL
)
1765 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
1766 return STATUS_INSUFFICIENT_RESOURCES
;
1769 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
1770 if (!NT_SUCCESS(Status
))
1772 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
1773 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
1777 *MFTIndex
= CurrentMFTIndex
;
1779 return STATUS_SUCCESS
;
1783 NtfsLookupFile(PDEVICE_EXTENSION Vcb
,
1784 PUNICODE_STRING PathName
,
1785 PFILE_RECORD_HEADER
*FileRecord
,
1786 PULONGLONG MFTIndex
)
1788 return NtfsLookupFileAt(Vcb
, PathName
, FileRecord
, MFTIndex
, NTFS_FILE_ROOT
);
1792 * @name NtfsDumpFileRecord
1795 * Provides diagnostic information about a file record. Prints a hex dump
1796 * of the entire record (based on the size reported by FileRecord->ByesInUse),
1797 * then prints a dump of each attribute.
1800 * Pointer to a DEVICE_EXTENSION describing the volume.
1803 * Pointer to the file record to be analyzed.
1806 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
1807 * in size, and not just the header.
1811 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb
,
1812 PFILE_RECORD_HEADER FileRecord
)
1816 // dump binary data, 8 bytes at a time
1817 for (i
= 0; i
< FileRecord
->BytesInUse
; i
+= 8)
1819 // display current offset, in hex
1820 DbgPrint("\t%03x\t", i
);
1822 // display hex value of each of the next 8 bytes
1823 for (j
= 0; j
< 8; j
++)
1824 DbgPrint("%02x ", *(PUCHAR
)((ULONG_PTR
)FileRecord
+ i
+ j
));
1828 NtfsDumpFileAttributes(Vcb
, FileRecord
);
1832 NtfsFindFileAt(PDEVICE_EXTENSION Vcb
,
1833 PUNICODE_STRING SearchPattern
,
1835 PFILE_RECORD_HEADER
*FileRecord
,
1836 PULONGLONG MFTIndex
,
1837 ULONGLONG CurrentMFTIndex
)
1841 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb
, SearchPattern
, *FirstEntry
, FileRecord
, MFTIndex
, CurrentMFTIndex
);
1843 Status
= NtfsFindMftRecord(Vcb
, CurrentMFTIndex
, SearchPattern
, FirstEntry
, TRUE
, &CurrentMFTIndex
);
1844 if (!NT_SUCCESS(Status
))
1846 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status
);
1850 *FileRecord
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1851 if (*FileRecord
== NULL
)
1853 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
1854 return STATUS_INSUFFICIENT_RESOURCES
;
1857 Status
= ReadFileRecord(Vcb
, CurrentMFTIndex
, *FileRecord
);
1858 if (!NT_SUCCESS(Status
))
1860 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
1861 ExFreePoolWithTag(*FileRecord
, TAG_NTFS
);
1865 *MFTIndex
= CurrentMFTIndex
;
1867 return STATUS_SUCCESS
;