3 * Copyright (C) 2002,2003 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/attrib.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Eric Kohl
25 * Hervé Poussineau (hpoussin@reactos.org)
26 * Pierre Schweitzer (pierre@reactos.org)
29 /* INCLUDES *****************************************************************/
32 #include <ntintsafe.h>
37 /* FUNCTIONS ****************************************************************/
43 * Adds a $BITMAP attribute to a given FileRecord.
46 * Pointer to an NTFS_VCB for the destination volume.
49 * Pointer to a complete file record to add the attribute to.
51 * @param AttributeAddress
52 * Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute.
53 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
56 * Pointer to a string of 16-bit Unicode characters naming the attribute. Most often L"$I30".
59 * The number of wide-characters in the name. L"$I30" Would use 4 here.
62 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
63 * of the given file record, or if the file record isn't large enough for the attribute.
66 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
67 * be of type AttributeEnd.
68 * This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space.
72 AddBitmap(PNTFS_VCB Vcb
,
73 PFILE_RECORD_HEADER FileRecord
,
74 PNTFS_ATTR_RECORD AttributeAddress
,
78 ULONG AttributeLength
;
79 // Calculate the header length
80 ULONG ResidentHeaderLength
= FIELD_OFFSET(NTFS_ATTR_RECORD
, Resident
.Reserved
) + sizeof(UCHAR
);
81 ULONG FileRecordEnd
= AttributeAddress
->Length
;
84 // We'll start out with 8 bytes of bitmap data
85 ULONG ValueLength
= 8;
88 if (AttributeAddress
->Type
!= AttributeEnd
)
90 DPRINT1("FIXME: Can only add $BITMAP attribute to the end of a file record.\n");
91 return STATUS_NOT_IMPLEMENTED
;
94 NameOffset
= ResidentHeaderLength
;
96 // Calculate ValueOffset, which will be aligned to a 4-byte boundary
97 ValueOffset
= ALIGN_UP_BY(NameOffset
+ (sizeof(WCHAR
) * NameLength
), VALUE_OFFSET_ALIGNMENT
);
99 // Calculate length of attribute
100 AttributeLength
= ValueOffset
+ ValueLength
;
101 AttributeLength
= ALIGN_UP_BY(AttributeLength
, ATTR_RECORD_ALIGNMENT
);
103 // Make sure the file record is large enough for the new attribute
104 BytesAvailable
= Vcb
->NtfsInfo
.BytesPerFileRecord
- FileRecord
->BytesInUse
;
105 if (BytesAvailable
< AttributeLength
)
107 DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
108 return STATUS_NOT_IMPLEMENTED
;
111 // Set Attribute fields
112 RtlZeroMemory(AttributeAddress
, AttributeLength
);
114 AttributeAddress
->Type
= AttributeBitmap
;
115 AttributeAddress
->Length
= AttributeLength
;
116 AttributeAddress
->NameLength
= NameLength
;
117 AttributeAddress
->NameOffset
= NameOffset
;
118 AttributeAddress
->Instance
= FileRecord
->NextAttributeNumber
++;
120 AttributeAddress
->Resident
.ValueLength
= ValueLength
;
121 AttributeAddress
->Resident
.ValueOffset
= ValueOffset
;
124 RtlCopyMemory((PCHAR
)((ULONG_PTR
)AttributeAddress
+ NameOffset
), Name
, NameLength
* sizeof(WCHAR
));
126 // move the attribute-end and file-record-end markers to the end of the file record
127 AttributeAddress
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)AttributeAddress
+ AttributeAddress
->Length
);
128 SetFileRecordEnd(FileRecord
, AttributeAddress
, FileRecordEnd
);
130 return STATUS_SUCCESS
;
137 * Adds a $DATA attribute to a given FileRecord.
140 * Pointer to a complete file record to add the attribute to. Caller is responsible for
141 * ensuring FileRecord is large enough to contain $DATA.
143 * @param AttributeAddress
144 * Pointer to the region of memory that will receive the $DATA attribute.
145 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
148 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
149 * of the given file record.
152 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
153 * be of type AttributeEnd.
154 * As it's implemented, this function is only intended to assist in creating new file records. It
155 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
156 * It's the caller's responsibility to ensure the given file record has enough memory allocated
160 AddData(PFILE_RECORD_HEADER FileRecord
,
161 PNTFS_ATTR_RECORD AttributeAddress
)
163 ULONG ResidentHeaderLength
= FIELD_OFFSET(NTFS_ATTR_RECORD
, Resident
.Reserved
) + sizeof(UCHAR
);
164 ULONG FileRecordEnd
= AttributeAddress
->Length
;
166 if (AttributeAddress
->Type
!= AttributeEnd
)
168 DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
169 return STATUS_NOT_IMPLEMENTED
;
172 AttributeAddress
->Type
= AttributeData
;
173 AttributeAddress
->Length
= ResidentHeaderLength
;
174 AttributeAddress
->Length
= ALIGN_UP_BY(AttributeAddress
->Length
, ATTR_RECORD_ALIGNMENT
);
175 AttributeAddress
->Resident
.ValueLength
= 0;
176 AttributeAddress
->Resident
.ValueOffset
= ResidentHeaderLength
;
178 // for unnamed $DATA attributes, NameOffset equals header length
179 AttributeAddress
->NameOffset
= ResidentHeaderLength
;
180 AttributeAddress
->Instance
= FileRecord
->NextAttributeNumber
++;
182 // move the attribute-end and file-record-end markers to the end of the file record
183 AttributeAddress
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)AttributeAddress
+ AttributeAddress
->Length
);
184 SetFileRecordEnd(FileRecord
, AttributeAddress
, FileRecordEnd
);
186 return STATUS_SUCCESS
;
193 * Adds a $FILE_NAME attribute to a given FileRecord.
196 * Pointer to a complete file record to add the attribute to. Caller is responsible for
197 * ensuring FileRecord is large enough to contain $FILE_NAME.
199 * @param AttributeAddress
200 * Pointer to the region of memory that will receive the $FILE_NAME attribute.
201 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
204 * Points to the target disk's DEVICE_EXTENSION.
207 * Pointer to the FILE_OBJECT which represents the new name.
208 * This parameter is used to determine the filename and parent directory.
210 * @param CaseSensitive
211 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
212 * if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag.
214 * @param ParentMftIndex
215 * Pointer to a ULONGLONG which will receive the index of the parent directory.
218 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
219 * of the given file record.
222 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
223 * be of type AttributeEnd.
224 * As it's implemented, this function is only intended to assist in creating new file records. It
225 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
226 * It's the caller's responsibility to ensure the given file record has enough memory allocated
230 AddFileName(PFILE_RECORD_HEADER FileRecord
,
231 PNTFS_ATTR_RECORD AttributeAddress
,
232 PDEVICE_EXTENSION DeviceExt
,
233 PFILE_OBJECT FileObject
,
234 BOOLEAN CaseSensitive
,
235 PULONGLONG ParentMftIndex
)
237 ULONG ResidentHeaderLength
= FIELD_OFFSET(NTFS_ATTR_RECORD
, Resident
.Reserved
) + sizeof(UCHAR
);
238 PFILENAME_ATTRIBUTE FileNameAttribute
;
239 LARGE_INTEGER SystemTime
;
240 ULONG FileRecordEnd
= AttributeAddress
->Length
;
241 ULONGLONG CurrentMFTIndex
= NTFS_FILE_ROOT
;
242 UNICODE_STRING Current
, Remaining
, FilenameNoPath
;
243 NTSTATUS Status
= STATUS_SUCCESS
;
246 if (AttributeAddress
->Type
!= AttributeEnd
)
248 DPRINT1("FIXME: Can only add $FILE_NAME attribute to the end of a file record.\n");
249 return STATUS_NOT_IMPLEMENTED
;
252 AttributeAddress
->Type
= AttributeFileName
;
253 AttributeAddress
->Instance
= FileRecord
->NextAttributeNumber
++;
255 FileNameAttribute
= (PFILENAME_ATTRIBUTE
)((LONG_PTR
)AttributeAddress
+ ResidentHeaderLength
);
258 KeQuerySystemTime(&SystemTime
);
259 FileNameAttribute
->CreationTime
= SystemTime
.QuadPart
;
260 FileNameAttribute
->ChangeTime
= SystemTime
.QuadPart
;
261 FileNameAttribute
->LastWriteTime
= SystemTime
.QuadPart
;
262 FileNameAttribute
->LastAccessTime
= SystemTime
.QuadPart
;
264 // Is this a directory?
265 if(FileRecord
->Flags
& FRH_DIRECTORY
)
266 FileNameAttribute
->FileAttributes
= NTFS_FILE_TYPE_DIRECTORY
;
268 FileNameAttribute
->FileAttributes
= NTFS_FILE_TYPE_ARCHIVE
;
270 // we need to extract the filename from the path
271 DPRINT1("Pathname: %wZ\n", &FileObject
->FileName
);
273 FsRtlDissectName(FileObject
->FileName
, &Current
, &Remaining
);
275 FilenameNoPath
.Buffer
= Current
.Buffer
;
276 FilenameNoPath
.MaximumLength
= FilenameNoPath
.Length
= Current
.Length
;
278 while (Current
.Length
!= 0)
280 DPRINT1("Current: %wZ\n", &Current
);
282 if (Remaining
.Length
!= 0)
284 FilenameNoPath
.Buffer
= Remaining
.Buffer
;
285 FilenameNoPath
.Length
= FilenameNoPath
.MaximumLength
= Remaining
.Length
;
289 Status
= NtfsFindMftRecord(DeviceExt
,
296 if (!NT_SUCCESS(Status
))
299 if (Remaining
.Length
== 0 )
301 if (Current
.Length
!= 0)
303 FilenameNoPath
.Buffer
= Current
.Buffer
;
304 FilenameNoPath
.Length
= FilenameNoPath
.MaximumLength
= Current
.Length
;
309 FsRtlDissectName(Remaining
, &Current
, &Remaining
);
312 DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex
);
314 // set reference to parent directory
315 FileNameAttribute
->DirectoryFileReferenceNumber
= CurrentMFTIndex
;
316 *ParentMftIndex
= CurrentMFTIndex
;
318 DPRINT1("SequenceNumber: 0x%02x\n", FileRecord
->SequenceNumber
);
320 // The highest 2 bytes should be the sequence number, unless the parent happens to be root
321 if (CurrentMFTIndex
== NTFS_FILE_ROOT
)
322 FileNameAttribute
->DirectoryFileReferenceNumber
|= (ULONGLONG
)NTFS_FILE_ROOT
<< 48;
324 FileNameAttribute
->DirectoryFileReferenceNumber
|= (ULONGLONG
)FileRecord
->SequenceNumber
<< 48;
326 DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute
->DirectoryFileReferenceNumber
);
328 FileNameAttribute
->NameLength
= FilenameNoPath
.Length
/ sizeof(WCHAR
);
329 RtlCopyMemory(FileNameAttribute
->Name
, FilenameNoPath
.Buffer
, FilenameNoPath
.Length
);
331 // For now, we're emulating the way Windows behaves when 8.3 name generation is disabled
332 // TODO: add DOS Filename as needed
333 if (!CaseSensitive
&& RtlIsNameLegalDOS8Dot3(&FilenameNoPath
, NULL
, NULL
))
334 FileNameAttribute
->NameType
= NTFS_FILE_NAME_WIN32_AND_DOS
;
336 FileNameAttribute
->NameType
= NTFS_FILE_NAME_POSIX
;
338 FileRecord
->LinkCount
++;
340 AttributeAddress
->Length
= ResidentHeaderLength
+
341 FIELD_OFFSET(FILENAME_ATTRIBUTE
, Name
) + FilenameNoPath
.Length
;
342 AttributeAddress
->Length
= ALIGN_UP_BY(AttributeAddress
->Length
, ATTR_RECORD_ALIGNMENT
);
344 AttributeAddress
->Resident
.ValueLength
= FIELD_OFFSET(FILENAME_ATTRIBUTE
, Name
) + FilenameNoPath
.Length
;
345 AttributeAddress
->Resident
.ValueOffset
= ResidentHeaderLength
;
346 AttributeAddress
->Resident
.Flags
= RA_INDEXED
;
348 // move the attribute-end and file-record-end markers to the end of the file record
349 AttributeAddress
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)AttributeAddress
+ AttributeAddress
->Length
);
350 SetFileRecordEnd(FileRecord
, AttributeAddress
, FileRecordEnd
);
356 * @name AddIndexAllocation
359 * Adds an $INDEX_ALLOCATION attribute to a given FileRecord.
362 * Pointer to an NTFS_VCB for the destination volume.
365 * Pointer to a complete file record to add the attribute to.
367 * @param AttributeAddress
368 * Pointer to the region of memory that will receive the $INDEX_ALLOCATION attribute.
369 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
372 * Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30".
375 * The number of wide-characters in the name. L"$I30" Would use 4 here.
378 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
379 * of the given file record, or if the file record isn't large enough for the attribute.
382 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
383 * be of type AttributeEnd.
384 * This could be improved by adding an $ATTRIBUTE_LIST to the file record if there's not enough space.
388 AddIndexAllocation(PNTFS_VCB Vcb
,
389 PFILE_RECORD_HEADER FileRecord
,
390 PNTFS_ATTR_RECORD AttributeAddress
,
398 ULONG BytesAvailable
;
400 if (AttributeAddress
->Type
!= AttributeEnd
)
402 DPRINT1("FIXME: Can only add $INDEX_ALLOCATION attribute to the end of a file record.\n");
403 return STATUS_NOT_IMPLEMENTED
;
406 // Calculate the name offset
407 NameOffset
= FIELD_OFFSET(NTFS_ATTR_RECORD
, NonResident
.CompressedSize
);
409 // Calculate the offset to the first data run
410 DataRunOffset
= (sizeof(WCHAR
) * NameLength
) + NameOffset
;
411 // The data run offset must be aligned to a 4-byte boundary
412 DataRunOffset
= ALIGN_UP_BY(DataRunOffset
, DATA_RUN_ALIGNMENT
);
414 // Calculate the length of the new attribute; the empty data run will consist of a single byte
415 RecordLength
= DataRunOffset
+ 1;
417 // The size of the attribute itself must be aligned to an 8 - byte boundary
418 RecordLength
= ALIGN_UP_BY(RecordLength
, ATTR_RECORD_ALIGNMENT
);
420 // Back up the last 4-bytes of the file record (even though this value doesn't matter)
421 FileRecordEnd
= AttributeAddress
->Length
;
423 // Make sure the file record can contain the new attribute
424 BytesAvailable
= Vcb
->NtfsInfo
.BytesPerFileRecord
- FileRecord
->BytesInUse
;
425 if (BytesAvailable
< RecordLength
)
427 DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
428 return STATUS_NOT_IMPLEMENTED
;
431 // Set fields of attribute header
432 RtlZeroMemory(AttributeAddress
, RecordLength
);
434 AttributeAddress
->Type
= AttributeIndexAllocation
;
435 AttributeAddress
->Length
= RecordLength
;
436 AttributeAddress
->IsNonResident
= TRUE
;
437 AttributeAddress
->NameLength
= NameLength
;
438 AttributeAddress
->NameOffset
= NameOffset
;
439 AttributeAddress
->Instance
= FileRecord
->NextAttributeNumber
++;
441 AttributeAddress
->NonResident
.MappingPairsOffset
= DataRunOffset
;
442 AttributeAddress
->NonResident
.HighestVCN
= (LONGLONG
)-1;
445 RtlCopyMemory((PCHAR
)((ULONG_PTR
)AttributeAddress
+ NameOffset
), Name
, NameLength
* sizeof(WCHAR
));
447 // move the attribute-end and file-record-end markers to the end of the file record
448 AttributeAddress
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)AttributeAddress
+ AttributeAddress
->Length
);
449 SetFileRecordEnd(FileRecord
, AttributeAddress
, FileRecordEnd
);
451 return STATUS_SUCCESS
;
458 * Adds an $INDEX_ROOT attribute to a given FileRecord.
461 * Pointer to an NTFS_VCB for the destination volume.
464 * Pointer to a complete file record to add the attribute to. Caller is responsible for
465 * ensuring FileRecord is large enough to contain $INDEX_ROOT.
467 * @param AttributeAddress
468 * Pointer to the region of memory that will receive the $INDEX_ROOT attribute.
469 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
471 * @param NewIndexRoot
472 * Pointer to an INDEX_ROOT_ATTRIBUTE containing the index root that will be copied to the new attribute.
475 * The length of NewIndexRoot, in bytes.
478 * Pointer to a string of 16-bit Unicode characters naming the attribute. Most often, this will be L"$I30".
481 * The number of wide-characters in the name. L"$I30" Would use 4 here.
484 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
485 * of the given file record.
488 * This function is intended to assist in creating new folders.
489 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
490 * be of type AttributeEnd.
491 * It's the caller's responsibility to ensure the given file record has enough memory allocated
492 * for the attribute, and this memory must have been zeroed.
495 AddIndexRoot(PNTFS_VCB Vcb
,
496 PFILE_RECORD_HEADER FileRecord
,
497 PNTFS_ATTR_RECORD AttributeAddress
,
498 PINDEX_ROOT_ATTRIBUTE NewIndexRoot
,
503 ULONG AttributeLength
;
504 // Calculate the header length
505 ULONG ResidentHeaderLength
= FIELD_OFFSET(NTFS_ATTR_RECORD
, Resident
.Reserved
) + sizeof(UCHAR
);
506 // Back up the file record's final ULONG (even though it doesn't matter)
507 ULONG FileRecordEnd
= AttributeAddress
->Length
;
510 ULONG BytesAvailable
;
512 if (AttributeAddress
->Type
!= AttributeEnd
)
514 DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
515 return STATUS_NOT_IMPLEMENTED
;
518 NameOffset
= ResidentHeaderLength
;
520 // Calculate ValueOffset, which will be aligned to a 4-byte boundary
521 ValueOffset
= ALIGN_UP_BY(NameOffset
+ (sizeof(WCHAR
) * NameLength
), VALUE_OFFSET_ALIGNMENT
);
523 // Calculate length of attribute
524 AttributeLength
= ValueOffset
+ RootLength
;
525 AttributeLength
= ALIGN_UP_BY(AttributeLength
, ATTR_RECORD_ALIGNMENT
);
527 // Make sure the file record is large enough for the new attribute
528 BytesAvailable
= Vcb
->NtfsInfo
.BytesPerFileRecord
- FileRecord
->BytesInUse
;
529 if (BytesAvailable
< AttributeLength
)
531 DPRINT1("FIXME: Not enough room in file record for index allocation attribute!\n");
532 return STATUS_NOT_IMPLEMENTED
;
535 // Set Attribute fields
536 RtlZeroMemory(AttributeAddress
, AttributeLength
);
538 AttributeAddress
->Type
= AttributeIndexRoot
;
539 AttributeAddress
->Length
= AttributeLength
;
540 AttributeAddress
->NameLength
= NameLength
;
541 AttributeAddress
->NameOffset
= NameOffset
;
542 AttributeAddress
->Instance
= FileRecord
->NextAttributeNumber
++;
544 AttributeAddress
->Resident
.ValueLength
= RootLength
;
545 AttributeAddress
->Resident
.ValueOffset
= ValueOffset
;
548 RtlCopyMemory((PCHAR
)((ULONG_PTR
)AttributeAddress
+ NameOffset
), Name
, NameLength
* sizeof(WCHAR
));
550 // Copy the index root attribute
551 RtlCopyMemory((PCHAR
)((ULONG_PTR
)AttributeAddress
+ ValueOffset
), NewIndexRoot
, RootLength
);
553 // move the attribute-end and file-record-end markers to the end of the file record
554 AttributeAddress
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)AttributeAddress
+ AttributeAddress
->Length
);
555 SetFileRecordEnd(FileRecord
, AttributeAddress
, FileRecordEnd
);
557 return STATUS_SUCCESS
;
564 * Adds a run of allocated clusters to a non-resident attribute.
567 * Pointer to an NTFS_VCB for the destination volume.
570 * Pointer to an NTFS_ATTR_CONTEXT describing the destination attribute.
573 * Byte offset of the destination attribute relative to its file record.
576 * Pointer to a complete copy of the file record containing the destination attribute. Must be at least
577 * Vcb->NtfsInfo.BytesPerFileRecord bytes long.
579 * @param NextAssignedCluster
580 * Logical cluster number of the start of the data run being added.
583 * How many clusters are in the data run being added. Can't be 0.
586 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute.
587 * STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails or if we fail to allocate a
588 * buffer for the new data runs.
589 * STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if FsRtlAddLargeMcbEntry() fails.
590 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
591 * STATUS_NOT_IMPLEMENTED if we need to migrate the attribute to an attribute list (TODO).
594 * Clusters should have been allocated previously with NtfsAllocateClusters().
599 AddRun(PNTFS_VCB Vcb
,
600 PNTFS_ATTR_CONTEXT AttrContext
,
602 PFILE_RECORD_HEADER FileRecord
,
603 ULONGLONG NextAssignedCluster
,
607 int DataRunMaxLength
;
608 PNTFS_ATTR_RECORD DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
609 ULONG NextAttributeOffset
= AttrOffset
+ AttrContext
->pRecord
->Length
;
610 ULONGLONG NextVBN
= 0;
615 if (!AttrContext
->pRecord
->IsNonResident
)
616 return STATUS_INVALID_PARAMETER
;
618 if (AttrContext
->pRecord
->NonResident
.AllocatedSize
!= 0)
619 NextVBN
= AttrContext
->pRecord
->NonResident
.HighestVCN
+ 1;
621 // Add newly-assigned clusters to mcb
624 if (!FsRtlAddLargeMcbEntry(&AttrContext
->DataRunsMCB
,
629 ExRaiseStatus(STATUS_UNSUCCESSFUL
);
632 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
)
634 DPRINT1("Failed to add LargeMcb Entry!\n");
635 _SEH2_YIELD(return _SEH2_GetExceptionCode());
639 RunBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
642 DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
643 return STATUS_INSUFFICIENT_RESOURCES
;
646 // Convert the map control block back to encoded data runs
647 ConvertLargeMCBToDataRuns(&AttrContext
->DataRunsMCB
, RunBuffer
, Vcb
->NtfsInfo
.BytesPerCluster
, &RunBufferSize
);
649 // Get the amount of free space between the start of the of the first data run and the attribute end
650 DataRunMaxLength
= AttrContext
->pRecord
->Length
- AttrContext
->pRecord
->NonResident
.MappingPairsOffset
;
652 // Do we need to extend the attribute (or convert to attribute list)?
653 if (DataRunMaxLength
< RunBufferSize
)
655 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ NextAttributeOffset
);
656 PNTFS_ATTR_RECORD NewRecord
;
658 // Add free space at the end of the file record to DataRunMaxLength
659 DataRunMaxLength
+= Vcb
->NtfsInfo
.BytesPerFileRecord
- FileRecord
->BytesInUse
;
661 // Can we resize the attribute?
662 if (DataRunMaxLength
< RunBufferSize
)
664 DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d, RunBufferSize: %d\n", DataRunMaxLength
, RunBufferSize
);
665 ExFreePoolWithTag(RunBuffer
, TAG_NTFS
);
666 return STATUS_NOT_IMPLEMENTED
;
669 // Are there more attributes after the one we're resizing?
670 if (NextAttribute
->Type
!= AttributeEnd
)
672 PNTFS_ATTR_RECORD FinalAttribute
;
674 // Calculate where to move the trailing attributes
675 ULONG_PTR MoveTo
= (ULONG_PTR
)DestinationAttribute
+ AttrContext
->pRecord
->NonResident
.MappingPairsOffset
+ RunBufferSize
;
676 MoveTo
= ALIGN_UP_BY(MoveTo
, ATTR_RECORD_ALIGNMENT
);
678 DPRINT1("Moving attribute(s) after this one starting with type 0x%lx\n", NextAttribute
->Type
);
680 // Move the trailing attributes; FinalAttribute will point to the end marker
681 FinalAttribute
= MoveAttributes(Vcb
, NextAttribute
, NextAttributeOffset
, MoveTo
);
683 // set the file record end
684 SetFileRecordEnd(FileRecord
, FinalAttribute
, FILE_RECORD_END
);
687 // calculate position of end markers
688 NextAttributeOffset
= AttrOffset
+ AttrContext
->pRecord
->NonResident
.MappingPairsOffset
+ RunBufferSize
;
689 NextAttributeOffset
= ALIGN_UP_BY(NextAttributeOffset
, ATTR_RECORD_ALIGNMENT
);
691 // Update the length of the destination attribute
692 DestinationAttribute
->Length
= NextAttributeOffset
- AttrOffset
;
694 // Create a new copy of the attribute record
695 NewRecord
= ExAllocatePoolWithTag(NonPagedPool
, DestinationAttribute
->Length
, TAG_NTFS
);
696 RtlCopyMemory(NewRecord
, AttrContext
->pRecord
, AttrContext
->pRecord
->Length
);
697 NewRecord
->Length
= DestinationAttribute
->Length
;
699 // Free the old copy of the attribute record, which won't be large enough
700 ExFreePoolWithTag(AttrContext
->pRecord
, TAG_NTFS
);
702 // Set the attribute context's record to the new copy
703 AttrContext
->pRecord
= NewRecord
;
705 // if NextAttribute is the AttributeEnd marker
706 if (NextAttribute
->Type
== AttributeEnd
)
708 // End the file record
709 NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ NextAttributeOffset
);
710 SetFileRecordEnd(FileRecord
, NextAttribute
, FILE_RECORD_END
);
715 DestinationAttribute
->NonResident
.HighestVCN
=
716 AttrContext
->pRecord
->NonResident
.HighestVCN
= max(NextVBN
- 1 + RunLength
,
717 AttrContext
->pRecord
->NonResident
.HighestVCN
);
719 // Write data runs to destination attribute
720 RtlCopyMemory((PVOID
)((ULONG_PTR
)DestinationAttribute
+ DestinationAttribute
->NonResident
.MappingPairsOffset
),
724 // Update the attribute record in the attribute context
725 RtlCopyMemory((PVOID
)((ULONG_PTR
)AttrContext
->pRecord
+ AttrContext
->pRecord
->NonResident
.MappingPairsOffset
),
729 // Update the file record
730 Status
= UpdateFileRecord(Vcb
, AttrContext
->FileMFTIndex
, FileRecord
);
732 ExFreePoolWithTag(RunBuffer
, TAG_NTFS
);
734 NtfsDumpDataRuns((PUCHAR
)((ULONG_PTR
)DestinationAttribute
+ DestinationAttribute
->NonResident
.MappingPairsOffset
), 0);
740 * @name AddStandardInformation
743 * Adds a $STANDARD_INFORMATION attribute to a given FileRecord.
746 * Pointer to a complete file record to add the attribute to. Caller is responsible for
747 * ensuring FileRecord is large enough to contain $STANDARD_INFORMATION.
749 * @param AttributeAddress
750 * Pointer to the region of memory that will receive the $STANDARD_INFORMATION attribute.
751 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
754 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
755 * of the given file record.
758 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
759 * be of type AttributeEnd.
760 * As it's implemented, this function is only intended to assist in creating new file records. It
761 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
762 * It's the caller's responsibility to ensure the given file record has enough memory allocated
766 AddStandardInformation(PFILE_RECORD_HEADER FileRecord
,
767 PNTFS_ATTR_RECORD AttributeAddress
)
769 ULONG ResidentHeaderLength
= FIELD_OFFSET(NTFS_ATTR_RECORD
, Resident
.Reserved
) + sizeof(UCHAR
);
770 PSTANDARD_INFORMATION StandardInfo
= (PSTANDARD_INFORMATION
)((LONG_PTR
)AttributeAddress
+ ResidentHeaderLength
);
771 LARGE_INTEGER SystemTime
;
772 ULONG FileRecordEnd
= AttributeAddress
->Length
;
774 if (AttributeAddress
->Type
!= AttributeEnd
)
776 DPRINT1("FIXME: Can only add $STANDARD_INFORMATION attribute to the end of a file record.\n");
777 return STATUS_NOT_IMPLEMENTED
;
780 AttributeAddress
->Type
= AttributeStandardInformation
;
781 AttributeAddress
->Length
= sizeof(STANDARD_INFORMATION
) + ResidentHeaderLength
;
782 AttributeAddress
->Length
= ALIGN_UP_BY(AttributeAddress
->Length
, ATTR_RECORD_ALIGNMENT
);
783 AttributeAddress
->Resident
.ValueLength
= sizeof(STANDARD_INFORMATION
);
784 AttributeAddress
->Resident
.ValueOffset
= ResidentHeaderLength
;
785 AttributeAddress
->Instance
= FileRecord
->NextAttributeNumber
++;
787 // set dates and times
788 KeQuerySystemTime(&SystemTime
);
789 StandardInfo
->CreationTime
= SystemTime
.QuadPart
;
790 StandardInfo
->ChangeTime
= SystemTime
.QuadPart
;
791 StandardInfo
->LastWriteTime
= SystemTime
.QuadPart
;
792 StandardInfo
->LastAccessTime
= SystemTime
.QuadPart
;
793 StandardInfo
->FileAttribute
= NTFS_FILE_TYPE_ARCHIVE
;
795 // move the attribute-end and file-record-end markers to the end of the file record
796 AttributeAddress
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)AttributeAddress
+ AttributeAddress
->Length
);
797 SetFileRecordEnd(FileRecord
, AttributeAddress
, FileRecordEnd
);
799 return STATUS_SUCCESS
;
803 * @name ConvertDataRunsToLargeMCB
806 * Converts binary data runs to a map control block.
809 * Pointer to the run data
812 * Pointer to an unitialized LARGE_MCB structure.
815 * STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if we fail to
816 * initialize the mcb or add an entry.
819 * Initializes the LARGE_MCB pointed to by DataRunsMCB. If this function succeeds, you
820 * need to call FsRtlUninitializeLargeMcb() when you're done with DataRunsMCB. This
821 * function will ensure the LargeMCB has been unitialized in case of failure.
825 ConvertDataRunsToLargeMCB(PUCHAR DataRun
,
826 PLARGE_MCB DataRunsMCB
,
829 LONGLONG DataRunOffset
;
830 ULONGLONG DataRunLength
;
831 LONGLONG DataRunStartLCN
;
832 ULONGLONG LastLCN
= 0;
834 // Initialize the MCB, potentially catch an exception
836 FsRtlInitializeLargeMcb(DataRunsMCB
, NonPagedPool
);
837 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
) {
838 _SEH2_YIELD(return _SEH2_GetExceptionCode());
841 while (*DataRun
!= 0)
843 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
845 if (DataRunOffset
!= -1)
848 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
849 LastLCN
= DataRunStartLCN
;
852 if (!FsRtlAddLargeMcbEntry(DataRunsMCB
,
857 ExRaiseStatus(STATUS_UNSUCCESSFUL
);
859 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER
) {
860 FsRtlUninitializeLargeMcb(DataRunsMCB
);
861 _SEH2_YIELD(return _SEH2_GetExceptionCode());
866 *pNextVBN
+= DataRunLength
;
869 return STATUS_SUCCESS
;
873 * @name ConvertLargeMCBToDataRuns
876 * Converts a map control block to a series of encoded data runs (used by non-resident attributes).
879 * Pointer to a LARGE_MCB structure describing the data runs.
882 * Pointer to the buffer that will receive the encoded data runs.
884 * @param MaxBufferSize
885 * Size of RunBuffer, in bytes.
887 * @param UsedBufferSize
888 * Pointer to a ULONG that will receive the size of the data runs in bytes. Can't be NULL.
891 * STATUS_SUCCESS on success, STATUS_BUFFER_TOO_SMALL if RunBuffer is too small to contain the
896 ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB
,
899 PULONG UsedBufferSize
)
901 NTSTATUS Status
= STATUS_SUCCESS
;
902 ULONG RunBufferOffset
= 0;
903 LONGLONG DataRunOffset
;
904 ULONGLONG LastLCN
= 0;
905 LONGLONG Vbn
, Lbn
, Count
;
909 DPRINT("\t[Vbn, Lbn, Count]\n");
911 // convert each mcb entry to a data run
912 for (i
= 0; FsRtlGetNextLargeMcbEntry(DataRunsMCB
, i
, &Vbn
, &Lbn
, &Count
); i
++)
914 UCHAR DataRunOffsetSize
= 0;
915 UCHAR DataRunLengthSize
= 0;
916 UCHAR ControlByte
= 0;
919 DPRINT("\t[%I64d, %I64d,%I64d]\n", Vbn
, Lbn
, Count
);
921 // TODO: check for holes and convert to sparse runs
922 DataRunOffset
= Lbn
- LastLCN
;
925 // now we need to determine how to represent DataRunOffset with the minimum number of bytes
926 DPRINT("Determining how many bytes needed to represent %I64x\n", DataRunOffset
);
927 DataRunOffsetSize
= GetPackedByteCount(DataRunOffset
, TRUE
);
928 DPRINT("%d bytes needed.\n", DataRunOffsetSize
);
930 // determine how to represent DataRunLengthSize with the minimum number of bytes
931 DPRINT("Determining how many bytes needed to represent %I64x\n", Count
);
932 DataRunLengthSize
= GetPackedByteCount(Count
, TRUE
);
933 DPRINT("%d bytes needed.\n", DataRunLengthSize
);
935 // ensure the next data run + end marker would be <= Max buffer size
936 if (RunBufferOffset
+ 2 + DataRunLengthSize
+ DataRunOffsetSize
> MaxBufferSize
)
938 Status
= STATUS_BUFFER_TOO_SMALL
;
939 DPRINT1("FIXME: Ran out of room in buffer for data runs!\n");
943 // pack and copy the control byte
944 ControlByte
= (DataRunOffsetSize
<< 4) + DataRunLengthSize
;
945 RunBuffer
[RunBufferOffset
++] = ControlByte
;
947 // copy DataRunLength
948 RtlCopyMemory(RunBuffer
+ RunBufferOffset
, &Count
, DataRunLengthSize
);
949 RunBufferOffset
+= DataRunLengthSize
;
951 // copy DataRunOffset
952 RtlCopyMemory(RunBuffer
+ RunBufferOffset
, &DataRunOffset
, DataRunOffsetSize
);
953 RunBufferOffset
+= DataRunOffsetSize
;
957 RunBuffer
[RunBufferOffset
++] = 0;
959 *UsedBufferSize
= RunBufferOffset
;
960 DPRINT("New Size of DataRuns: %ld\n", *UsedBufferSize
);
966 DecodeRun(PUCHAR DataRun
,
967 LONGLONG
*DataRunOffset
,
968 ULONGLONG
*DataRunLength
)
970 UCHAR DataRunOffsetSize
;
971 UCHAR DataRunLengthSize
;
974 DataRunOffsetSize
= (*DataRun
>> 4) & 0xF;
975 DataRunLengthSize
= *DataRun
& 0xF;
979 for (i
= 0; i
< DataRunLengthSize
; i
++)
981 *DataRunLength
+= ((ULONG64
)*DataRun
) << (i
* 8);
985 /* NTFS 3+ sparse files */
986 if (DataRunOffsetSize
== 0)
992 for (i
= 0; i
< DataRunOffsetSize
- 1; i
++)
994 *DataRunOffset
+= ((ULONG64
)*DataRun
) << (i
* 8);
997 /* The last byte contains sign so we must process it different way. */
998 *DataRunOffset
= ((LONG64
)(CHAR
)(*(DataRun
++)) << (i
* 8)) + *DataRunOffset
;
1001 DPRINT("DataRunOffsetSize: %x\n", DataRunOffsetSize
);
1002 DPRINT("DataRunLengthSize: %x\n", DataRunLengthSize
);
1003 DPRINT("DataRunOffset: %x\n", *DataRunOffset
);
1004 DPRINT("DataRunLength: %x\n", *DataRunLength
);
1010 FindRun(PNTFS_ATTR_RECORD NresAttr
,
1015 if (vcn
< NresAttr
->NonResident
.LowestVCN
|| vcn
> NresAttr
->NonResident
.HighestVCN
)
1018 DecodeRun((PUCHAR
)((ULONG_PTR
)NresAttr
+ NresAttr
->NonResident
.MappingPairsOffset
), (PLONGLONG
)lcn
, count
);
1024 * @name FreeClusters
1027 * Shrinks the allocation size of a non-resident attribute by a given number of clusters.
1028 * Frees the clusters from the volume's $BITMAP file as well as the attribute's data runs.
1031 * Pointer to an NTFS_VCB for the destination volume.
1033 * @param AttrContext
1034 * Pointer to an NTFS_ATTR_CONTEXT describing the attribute from which the clusters will be freed.
1037 * Byte offset of the destination attribute relative to its file record.
1040 * Pointer to a complete copy of the file record containing the attribute. Must be at least
1041 * Vcb->NtfsInfo.BytesPerFileRecord bytes long.
1043 * @param ClustersToFree
1044 * Number of clusters that should be freed from the end of the data stream. Must be no more
1045 * Than the number of clusters assigned to the attribute (HighestVCN + 1).
1048 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute,
1049 * or if the caller requested more clusters be freed than the attribute has been allocated.
1050 * STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or
1051 * if ConvertDataRunsToLargeMCB() fails.
1052 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
1057 FreeClusters(PNTFS_VCB Vcb
,
1058 PNTFS_ATTR_CONTEXT AttrContext
,
1060 PFILE_RECORD_HEADER FileRecord
,
1061 ULONG ClustersToFree
)
1063 NTSTATUS Status
= STATUS_SUCCESS
;
1064 ULONG ClustersLeftToFree
= ClustersToFree
;
1066 PNTFS_ATTR_RECORD DestinationAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ AttrOffset
);
1067 ULONG NextAttributeOffset
= AttrOffset
+ AttrContext
->pRecord
->Length
;
1068 PNTFS_ATTR_RECORD NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ NextAttributeOffset
);
1071 ULONG RunBufferSize
= 0;
1073 PFILE_RECORD_HEADER BitmapRecord
;
1074 PNTFS_ATTR_CONTEXT DataContext
;
1075 ULONGLONG BitmapDataSize
;
1078 ULONG LengthWritten
;
1080 if (!AttrContext
->pRecord
->IsNonResident
)
1082 return STATUS_INVALID_PARAMETER
;
1085 // Read the $Bitmap file
1086 BitmapRecord
= ExAllocateFromNPagedLookasideList(&Vcb
->FileRecLookasideList
);
1087 if (BitmapRecord
== NULL
)
1089 DPRINT1("Error: Unable to allocate memory for bitmap file record!\n");
1090 return STATUS_NO_MEMORY
;
1093 Status
= ReadFileRecord(Vcb
, NTFS_FILE_BITMAP
, BitmapRecord
);
1094 if (!NT_SUCCESS(Status
))
1096 DPRINT1("Error: Unable to read file record for bitmap!\n");
1097 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BitmapRecord
);
1101 Status
= FindAttribute(Vcb
, BitmapRecord
, AttributeData
, L
"", 0, &DataContext
, NULL
);
1102 if (!NT_SUCCESS(Status
))
1104 DPRINT1("Error: Unable to find data attribute for bitmap file!\n");
1105 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BitmapRecord
);
1109 BitmapDataSize
= AttributeDataLength(DataContext
->pRecord
);
1110 BitmapDataSize
= min(BitmapDataSize
, ULONG_MAX
);
1111 ASSERT((BitmapDataSize
* 8) >= Vcb
->NtfsInfo
.ClusterCount
);
1112 BitmapData
= ExAllocatePoolWithTag(NonPagedPool
, ROUND_UP(BitmapDataSize
, Vcb
->NtfsInfo
.BytesPerSector
), TAG_NTFS
);
1113 if (BitmapData
== NULL
)
1115 DPRINT1("Error: Unable to allocate memory for bitmap file data!\n");
1116 ReleaseAttributeContext(DataContext
);
1117 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BitmapRecord
);
1121 ReadAttribute(Vcb
, DataContext
, 0, (PCHAR
)BitmapData
, (ULONG
)BitmapDataSize
);
1123 RtlInitializeBitMap(&Bitmap
, (PULONG
)BitmapData
, Vcb
->NtfsInfo
.ClusterCount
);
1125 // free clusters in $BITMAP file
1126 while (ClustersLeftToFree
> 0)
1128 LONGLONG LargeVbn
, LargeLbn
;
1130 if (!FsRtlLookupLastLargeMcbEntry(&AttrContext
->DataRunsMCB
, &LargeVbn
, &LargeLbn
))
1132 Status
= STATUS_INVALID_PARAMETER
;
1133 DPRINT1("DRIVER ERROR: FreeClusters called to free %lu clusters, which is %lu more clusters than are assigned to attribute!",
1135 ClustersLeftToFree
);
1141 // deallocate this cluster
1142 RtlClearBits(&Bitmap
, LargeLbn
, 1);
1144 FsRtlTruncateLargeMcb(&AttrContext
->DataRunsMCB
, AttrContext
->pRecord
->NonResident
.HighestVCN
);
1146 // decrement HighestVCN, but don't let it go below 0
1147 AttrContext
->pRecord
->NonResident
.HighestVCN
= min(AttrContext
->pRecord
->NonResident
.HighestVCN
, AttrContext
->pRecord
->NonResident
.HighestVCN
- 1);
1148 ClustersLeftToFree
--;
1151 // update $BITMAP file on disk
1152 Status
= WriteAttribute(Vcb
, DataContext
, 0, BitmapData
, (ULONG
)BitmapDataSize
, &LengthWritten
, FileRecord
);
1153 if (!NT_SUCCESS(Status
))
1155 ReleaseAttributeContext(DataContext
);
1156 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1157 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BitmapRecord
);
1161 ReleaseAttributeContext(DataContext
);
1162 ExFreePoolWithTag(BitmapData
, TAG_NTFS
);
1163 ExFreeToNPagedLookasideList(&Vcb
->FileRecLookasideList
, BitmapRecord
);
1165 // Save updated data runs to file record
1167 // Allocate some memory for a new RunBuffer
1168 RunBuffer
= ExAllocatePoolWithTag(NonPagedPool
, Vcb
->NtfsInfo
.BytesPerFileRecord
, TAG_NTFS
);
1171 DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
1172 return STATUS_INSUFFICIENT_RESOURCES
;
1175 // Convert the map control block back to encoded data runs
1176 ConvertLargeMCBToDataRuns(&AttrContext
->DataRunsMCB
, RunBuffer
, Vcb
->NtfsInfo
.BytesPerCluster
, &RunBufferSize
);
1178 // Update HighestVCN
1179 DestinationAttribute
->NonResident
.HighestVCN
= AttrContext
->pRecord
->NonResident
.HighestVCN
;
1181 // Write data runs to destination attribute
1182 RtlCopyMemory((PVOID
)((ULONG_PTR
)DestinationAttribute
+ DestinationAttribute
->NonResident
.MappingPairsOffset
),
1186 // Is DestinationAttribute the last attribute in the file record?
1187 if (NextAttribute
->Type
== AttributeEnd
)
1189 // update attribute length
1190 DestinationAttribute
->Length
= ALIGN_UP_BY(AttrContext
->pRecord
->NonResident
.MappingPairsOffset
+ RunBufferSize
,
1191 ATTR_RECORD_ALIGNMENT
);
1193 ASSERT(DestinationAttribute
->Length
<= AttrContext
->pRecord
->Length
);
1195 AttrContext
->pRecord
->Length
= DestinationAttribute
->Length
;
1197 // write end markers
1198 NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)DestinationAttribute
+ DestinationAttribute
->Length
);
1199 SetFileRecordEnd(FileRecord
, NextAttribute
, FILE_RECORD_END
);
1202 // Update the file record
1203 Status
= UpdateFileRecord(Vcb
, AttrContext
->FileMFTIndex
, FileRecord
);
1205 ExFreePoolWithTag(RunBuffer
, TAG_NTFS
);
1207 NtfsDumpDataRuns((PUCHAR
)((ULONG_PTR
)DestinationAttribute
+ DestinationAttribute
->NonResident
.MappingPairsOffset
), 0);
1214 InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context
)
1217 PNTFS_ATTR_RECORD Attribute
;
1218 PNTFS_ATTR_CONTEXT ListContext
;
1220 DPRINT("InternalReadNonResidentAttributes(%p)\n", Context
);
1222 Attribute
= Context
->CurrAttr
;
1223 ASSERT(Attribute
->Type
== AttributeAttributeList
);
1225 if (Context
->OnlyResident
)
1227 Context
->NonResidentStart
= NULL
;
1228 Context
->NonResidentEnd
= NULL
;
1229 return STATUS_SUCCESS
;
1232 if (Context
->NonResidentStart
!= NULL
)
1234 return STATUS_FILE_CORRUPT_ERROR
;
1237 ListContext
= PrepareAttributeContext(Attribute
);
1238 ListSize
= AttributeDataLength(ListContext
->pRecord
);
1239 if (ListSize
> 0xFFFFFFFF)
1241 ReleaseAttributeContext(ListContext
);
1242 return STATUS_BUFFER_OVERFLOW
;
1245 Context
->NonResidentStart
= ExAllocatePoolWithTag(NonPagedPool
, (ULONG
)ListSize
, TAG_NTFS
);
1246 if (Context
->NonResidentStart
== NULL
)
1248 ReleaseAttributeContext(ListContext
);
1249 return STATUS_INSUFFICIENT_RESOURCES
;
1252 if (ReadAttribute(Context
->Vcb
, ListContext
, 0, (PCHAR
)Context
->NonResidentStart
, (ULONG
)ListSize
) != ListSize
)
1254 ExFreePoolWithTag(Context
->NonResidentStart
, TAG_NTFS
);
1255 Context
->NonResidentStart
= NULL
;
1256 ReleaseAttributeContext(ListContext
);
1257 return STATUS_FILE_CORRUPT_ERROR
;
1260 ReleaseAttributeContext(ListContext
);
1261 Context
->NonResidentEnd
= (PNTFS_ATTR_RECORD
)((PCHAR
)Context
->NonResidentStart
+ ListSize
);
1262 return STATUS_SUCCESS
;
1267 InternalGetNextAttribute(PFIND_ATTR_CONTXT Context
)
1269 PNTFS_ATTR_RECORD NextAttribute
;
1271 if (Context
->CurrAttr
== (PVOID
)-1)
1276 if (Context
->CurrAttr
>= Context
->FirstAttr
&&
1277 Context
->CurrAttr
< Context
->LastAttr
)
1279 if (Context
->CurrAttr
->Length
== 0)
1281 DPRINT1("Broken length!\n");
1282 Context
->CurrAttr
= (PVOID
)-1;
1286 NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Context
->CurrAttr
+ Context
->CurrAttr
->Length
);
1288 if (NextAttribute
> Context
->LastAttr
|| NextAttribute
< Context
->FirstAttr
)
1290 DPRINT1("Broken length: 0x%lx!\n", Context
->CurrAttr
->Length
);
1291 Context
->CurrAttr
= (PVOID
)-1;
1295 Context
->Offset
+= ((ULONG_PTR
)NextAttribute
- (ULONG_PTR
)Context
->CurrAttr
);
1296 Context
->CurrAttr
= NextAttribute
;
1298 if (Context
->CurrAttr
< Context
->LastAttr
&&
1299 Context
->CurrAttr
->Type
!= AttributeEnd
)
1301 return Context
->CurrAttr
;
1305 if (Context
->NonResidentStart
== NULL
)
1307 Context
->CurrAttr
= (PVOID
)-1;
1311 if (Context
->CurrAttr
< Context
->NonResidentStart
||
1312 Context
->CurrAttr
>= Context
->NonResidentEnd
)
1314 Context
->CurrAttr
= Context
->NonResidentStart
;
1316 else if (Context
->CurrAttr
->Length
!= 0)
1318 NextAttribute
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)Context
->CurrAttr
+ Context
->CurrAttr
->Length
);
1319 Context
->Offset
+= ((ULONG_PTR
)NextAttribute
- (ULONG_PTR
)Context
->CurrAttr
);
1320 Context
->CurrAttr
= NextAttribute
;
1324 DPRINT1("Broken length!\n");
1325 Context
->CurrAttr
= (PVOID
)-1;
1329 if (Context
->CurrAttr
< Context
->NonResidentEnd
&&
1330 Context
->CurrAttr
->Type
!= AttributeEnd
)
1332 return Context
->CurrAttr
;
1335 Context
->CurrAttr
= (PVOID
)-1;
1340 FindFirstAttribute(PFIND_ATTR_CONTXT Context
,
1341 PDEVICE_EXTENSION Vcb
,
1342 PFILE_RECORD_HEADER FileRecord
,
1343 BOOLEAN OnlyResident
,
1344 PNTFS_ATTR_RECORD
* Attribute
)
1348 DPRINT("FindFistAttribute(%p, %p, %p, %p, %u, %p)\n", Context
, Vcb
, FileRecord
, OnlyResident
, Attribute
);
1351 Context
->OnlyResident
= OnlyResident
;
1352 Context
->FirstAttr
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ FileRecord
->AttributeOffset
);
1353 Context
->CurrAttr
= Context
->FirstAttr
;
1354 Context
->LastAttr
= (PNTFS_ATTR_RECORD
)((ULONG_PTR
)FileRecord
+ FileRecord
->BytesInUse
);
1355 Context
->NonResidentStart
= NULL
;
1356 Context
->NonResidentEnd
= NULL
;
1357 Context
->Offset
= FileRecord
->AttributeOffset
;
1359 if (Context
->FirstAttr
->Type
== AttributeEnd
)
1361 Context
->CurrAttr
= (PVOID
)-1;
1362 return STATUS_END_OF_FILE
;
1364 else if (Context
->FirstAttr
->Type
== AttributeAttributeList
)
1366 Status
= InternalReadNonResidentAttributes(Context
);
1367 if (!NT_SUCCESS(Status
))
1372 *Attribute
= InternalGetNextAttribute(Context
);
1373 if (*Attribute
== NULL
)
1375 return STATUS_END_OF_FILE
;
1380 *Attribute
= Context
->CurrAttr
;
1381 Context
->Offset
= (UCHAR
*)Context
->CurrAttr
- (UCHAR
*)FileRecord
;
1384 return STATUS_SUCCESS
;
1388 FindNextAttribute(PFIND_ATTR_CONTXT Context
,
1389 PNTFS_ATTR_RECORD
* Attribute
)
1393 DPRINT("FindNextAttribute(%p, %p)\n", Context
, Attribute
);
1395 *Attribute
= InternalGetNextAttribute(Context
);
1396 if (*Attribute
== NULL
)
1398 return STATUS_END_OF_FILE
;
1401 if (Context
->CurrAttr
->Type
!= AttributeAttributeList
)
1403 return STATUS_SUCCESS
;
1406 Status
= InternalReadNonResidentAttributes(Context
);
1407 if (!NT_SUCCESS(Status
))
1412 *Attribute
= InternalGetNextAttribute(Context
);
1413 if (*Attribute
== NULL
)
1415 return STATUS_END_OF_FILE
;
1418 return STATUS_SUCCESS
;
1422 FindCloseAttribute(PFIND_ATTR_CONTXT Context
)
1424 if (Context
->NonResidentStart
!= NULL
)
1426 ExFreePoolWithTag(Context
->NonResidentStart
, TAG_NTFS
);
1427 Context
->NonResidentStart
= NULL
;
1433 NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute
)
1435 PFILENAME_ATTRIBUTE FileNameAttr
;
1437 DbgPrint(" $FILE_NAME ");
1439 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1441 FileNameAttr
= (PFILENAME_ATTRIBUTE
)((ULONG_PTR
)Attribute
+ Attribute
->Resident
.ValueOffset
);
1442 DbgPrint(" (%x) '%.*S' ", FileNameAttr
->NameType
, FileNameAttr
->NameLength
, FileNameAttr
->Name
);
1443 DbgPrint(" '%x' \n", FileNameAttr
->FileAttributes
);
1444 DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr
->AllocatedSize
, FileNameAttr
->DataSize
);
1445 DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr
->DirectoryFileReferenceNumber
);
1451 NtfsDumpStandardInformationAttribute(PNTFS_ATTR_RECORD Attribute
)
1453 PSTANDARD_INFORMATION StandardInfoAttr
;
1455 DbgPrint(" $STANDARD_INFORMATION ");
1457 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1459 StandardInfoAttr
= (PSTANDARD_INFORMATION
)((ULONG_PTR
)Attribute
+ Attribute
->Resident
.ValueOffset
);
1460 DbgPrint(" '%x' ", StandardInfoAttr
->FileAttribute
);
1466 NtfsDumpVolumeNameAttribute(PNTFS_ATTR_RECORD Attribute
)
1470 DbgPrint(" $VOLUME_NAME ");
1472 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1474 VolumeName
= (PWCHAR
)((ULONG_PTR
)Attribute
+ Attribute
->Resident
.ValueOffset
);
1475 DbgPrint(" '%.*S' ", Attribute
->Resident
.ValueLength
/ sizeof(WCHAR
), VolumeName
);
1481 NtfsDumpVolumeInformationAttribute(PNTFS_ATTR_RECORD Attribute
)
1483 PVOLINFO_ATTRIBUTE VolInfoAttr
;
1485 DbgPrint(" $VOLUME_INFORMATION ");
1487 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1489 VolInfoAttr
= (PVOLINFO_ATTRIBUTE
)((ULONG_PTR
)Attribute
+ Attribute
->Resident
.ValueOffset
);
1490 DbgPrint(" NTFS Version %u.%u Flags 0x%04hx ",
1491 VolInfoAttr
->MajorVersion
,
1492 VolInfoAttr
->MinorVersion
,
1493 VolInfoAttr
->Flags
);
1499 NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute
)
1501 PINDEX_ROOT_ATTRIBUTE IndexRootAttr
;
1502 ULONG CurrentOffset
;
1505 IndexRootAttr
= (PINDEX_ROOT_ATTRIBUTE
)((ULONG_PTR
)Attribute
+ Attribute
->Resident
.ValueOffset
);
1507 if (IndexRootAttr
->AttributeType
== AttributeFileName
)
1508 ASSERT(IndexRootAttr
->CollationRule
== COLLATION_FILE_NAME
);
1510 DbgPrint(" $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr
->SizeOfEntry
, IndexRootAttr
->ClustersPerIndexRecord
);
1512 if (IndexRootAttr
->Header
.Flags
== INDEX_ROOT_SMALL
)
1514 DbgPrint(" (small)\n");
1518 ASSERT(IndexRootAttr
->Header
.Flags
== INDEX_ROOT_LARGE
);
1519 DbgPrint(" (large)\n");
1522 DbgPrint(" Offset to first index: 0x%lx\n Total size of index entries: 0x%lx\n Allocated size of node: 0x%lx\n",
1523 IndexRootAttr
->Header
.FirstEntryOffset
,
1524 IndexRootAttr
->Header
.TotalSizeOfEntries
,
1525 IndexRootAttr
->Header
.AllocatedSize
);
1526 CurrentOffset
= IndexRootAttr
->Header
.FirstEntryOffset
;
1528 // print details of every node in the index
1529 while (CurrentOffset
< IndexRootAttr
->Header
.TotalSizeOfEntries
)
1531 PINDEX_ENTRY_ATTRIBUTE currentIndexExtry
= (PINDEX_ENTRY_ATTRIBUTE
)((ULONG_PTR
)IndexRootAttr
+ 0x10 + CurrentOffset
);
1532 DbgPrint(" Index Node Entry %lu", CurrentNode
++);
1533 if (BooleanFlagOn(currentIndexExtry
->Flags
, NTFS_INDEX_ENTRY_NODE
))
1534 DbgPrint(" (Branch)");
1536 DbgPrint(" (Leaf)");
1537 if (BooleanFlagOn(currentIndexExtry
->Flags
, NTFS_INDEX_ENTRY_END
))
1539 DbgPrint(" (Dummy Key)");
1541 DbgPrint("\n File Reference: 0x%016I64x\n", currentIndexExtry
->Data
.Directory
.IndexedFile
);
1542 DbgPrint(" Index Entry Length: 0x%x\n", currentIndexExtry
->Length
);
1543 DbgPrint(" Index Key Length: 0x%x\n", currentIndexExtry
->KeyLength
);
1545 // if this isn't the final (dummy) node, print info about the key (Filename attribute)
1546 if (!(currentIndexExtry
->Flags
& NTFS_INDEX_ENTRY_END
))
1548 UNICODE_STRING Name
;
1549 DbgPrint(" Parent File Reference: 0x%016I64x\n", currentIndexExtry
->FileName
.DirectoryFileReferenceNumber
);
1550 DbgPrint(" $FILENAME indexed: ");
1551 Name
.Length
= currentIndexExtry
->FileName
.NameLength
* sizeof(WCHAR
);
1552 Name
.MaximumLength
= Name
.Length
;
1553 Name
.Buffer
= currentIndexExtry
->FileName
.Name
;
1554 DbgPrint("'%wZ'\n", &Name
);
1557 // if this node has a sub-node beneath it
1558 if (currentIndexExtry
->Flags
& NTFS_INDEX_ENTRY_NODE
)
1560 // Print the VCN of the sub-node
1561 PULONGLONG SubNodeVCN
= (PULONGLONG
)((ULONG_PTR
)currentIndexExtry
+ currentIndexExtry
->Length
- sizeof(ULONGLONG
));
1562 DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN
);
1565 CurrentOffset
+= currentIndexExtry
->Length
;
1566 ASSERT(currentIndexExtry
->Length
);
1574 NtfsDumpAttribute(PDEVICE_EXTENSION Vcb
,
1575 PNTFS_ATTR_RECORD Attribute
)
1577 UNICODE_STRING Name
;
1580 ULONGLONG runcount
= 0;
1582 switch (Attribute
->Type
)
1584 case AttributeFileName
:
1585 NtfsDumpFileNameAttribute(Attribute
);
1588 case AttributeStandardInformation
:
1589 NtfsDumpStandardInformationAttribute(Attribute
);
1592 case AttributeObjectId
:
1593 DbgPrint(" $OBJECT_ID ");
1596 case AttributeSecurityDescriptor
:
1597 DbgPrint(" $SECURITY_DESCRIPTOR ");
1600 case AttributeVolumeName
:
1601 NtfsDumpVolumeNameAttribute(Attribute
);
1604 case AttributeVolumeInformation
:
1605 NtfsDumpVolumeInformationAttribute(Attribute
);
1609 DbgPrint(" $DATA ");
1610 //DataBuf = ExAllocatePool(NonPagedPool,AttributeLengthAllocated(Attribute));
1613 case AttributeIndexRoot
:
1614 NtfsDumpIndexRootAttribute(Attribute
);
1617 case AttributeIndexAllocation
:
1618 DbgPrint(" $INDEX_ALLOCATION ");
1621 case AttributeBitmap
:
1622 DbgPrint(" $BITMAP ");
1625 case AttributeReparsePoint
:
1626 DbgPrint(" $REPARSE_POINT ");
1629 case AttributeEAInformation
:
1630 DbgPrint(" $EA_INFORMATION ");
1637 case AttributePropertySet
:
1638 DbgPrint(" $PROPERTY_SET ");
1641 case AttributeLoggedUtilityStream
:
1642 DbgPrint(" $LOGGED_UTILITY_STREAM ");
1646 DbgPrint(" Attribute %lx ",
1651 if (Attribute
->Type
!= AttributeAttributeList
)
1653 if (Attribute
->NameLength
!= 0)
1655 Name
.Length
= Attribute
->NameLength
* sizeof(WCHAR
);
1656 Name
.MaximumLength
= Name
.Length
;
1657 Name
.Buffer
= (PWCHAR
)((ULONG_PTR
)Attribute
+ Attribute
->NameOffset
);
1659 DbgPrint("'%wZ' ", &Name
);
1663 Attribute
->IsNonResident
? "non-resident" : "resident");
1665 if (Attribute
->IsNonResident
)
1667 FindRun(Attribute
,0,&lcn
, &runcount
);
1669 DbgPrint(" AllocatedSize %I64u DataSize %I64u InitilizedSize %I64u\n",
1670 Attribute
->NonResident
.AllocatedSize
, Attribute
->NonResident
.DataSize
, Attribute
->NonResident
.InitializedSize
);
1671 DbgPrint(" logical clusters: %I64u - %I64u\n",
1672 lcn
, lcn
+ runcount
- 1);
1675 DbgPrint(" %u bytes of data\n", Attribute
->Resident
.ValueLength
);
1680 VOID
NtfsDumpDataRunData(PUCHAR DataRun
)
1682 UCHAR DataRunOffsetSize
;
1683 UCHAR DataRunLengthSize
;
1686 DbgPrint("%02x ", *DataRun
);
1691 DataRunOffsetSize
= (*DataRun
>> 4) & 0xF;
1692 DataRunLengthSize
= *DataRun
& 0xF;
1695 for (i
= 0; i
< DataRunLengthSize
; i
++)
1697 DbgPrint("%02x ", *DataRun
);
1701 for (i
= 0; i
< DataRunOffsetSize
; i
++)
1703 DbgPrint("%02x ", *DataRun
);
1707 NtfsDumpDataRunData(DataRun
);
1712 NtfsDumpDataRuns(PVOID StartOfRun
,
1713 ULONGLONG CurrentLCN
)
1715 PUCHAR DataRun
= StartOfRun
;
1716 LONGLONG DataRunOffset
;
1717 ULONGLONG DataRunLength
;
1719 if (CurrentLCN
== 0)
1721 DPRINT1("Dumping data runs.\n\tData:\n\t\t");
1722 NtfsDumpDataRunData(StartOfRun
);
1723 DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n");
1726 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1728 if (DataRunOffset
!= -1)
1729 CurrentLCN
+= DataRunOffset
;
1731 DbgPrint("\t\t%I64d\t", DataRunOffset
);
1732 if (DataRunOffset
< 99999)
1734 DbgPrint("%I64u\t", CurrentLCN
);
1735 if (CurrentLCN
< 99999)
1737 DbgPrint("%I64u\n", DataRunLength
);
1740 DbgPrint("\t\t00\n");
1742 NtfsDumpDataRuns(DataRun
, CurrentLCN
);
1747 NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb
,
1748 PFILE_RECORD_HEADER FileRecord
)
1751 FIND_ATTR_CONTXT Context
;
1752 PNTFS_ATTR_RECORD Attribute
;
1754 Status
= FindFirstAttribute(&Context
, Vcb
, FileRecord
, FALSE
, &Attribute
);
1755 while (NT_SUCCESS(Status
))
1757 NtfsDumpAttribute(Vcb
, Attribute
);
1759 Status
= FindNextAttribute(&Context
, &Attribute
);
1762 FindCloseAttribute(&Context
);
1766 GetFileNameFromRecord(PDEVICE_EXTENSION Vcb
,
1767 PFILE_RECORD_HEADER FileRecord
,
1770 FIND_ATTR_CONTXT Context
;
1771 PNTFS_ATTR_RECORD Attribute
;
1772 PFILENAME_ATTRIBUTE Name
;
1775 Status
= FindFirstAttribute(&Context
, Vcb
, FileRecord
, FALSE
, &Attribute
);
1776 while (NT_SUCCESS(Status
))
1778 if (Attribute
->Type
== AttributeFileName
)
1780 Name
= (PFILENAME_ATTRIBUTE
)((ULONG_PTR
)Attribute
+ Attribute
->Resident
.ValueOffset
);
1781 if (Name
->NameType
== NameType
||
1782 (Name
->NameType
== NTFS_FILE_NAME_WIN32_AND_DOS
&& NameType
== NTFS_FILE_NAME_WIN32
) ||
1783 (Name
->NameType
== NTFS_FILE_NAME_WIN32_AND_DOS
&& NameType
== NTFS_FILE_NAME_DOS
))
1785 FindCloseAttribute(&Context
);
1790 Status
= FindNextAttribute(&Context
, &Attribute
);
1793 FindCloseAttribute(&Context
);
1798 * GetPackedByteCount
1799 * Returns the minimum number of bytes needed to represent the value of a
1800 * 64-bit number. Used to encode data runs.
1803 GetPackedByteCount(LONGLONG NumberToPack
,
1808 if (NumberToPack
>= 0x0100000000000000)
1810 if (NumberToPack
>= 0x0001000000000000)
1812 if (NumberToPack
>= 0x0000010000000000)
1814 if (NumberToPack
>= 0x0000000100000000)
1816 if (NumberToPack
>= 0x0000000001000000)
1818 if (NumberToPack
>= 0x0000000000010000)
1820 if (NumberToPack
>= 0x0000000000000100)
1825 if (NumberToPack
> 0)
1827 // we have to make sure the number that gets encoded won't be interpreted as negative
1828 if (NumberToPack
>= 0x0080000000000000)
1830 if (NumberToPack
>= 0x0000800000000000)
1832 if (NumberToPack
>= 0x0000008000000000)
1834 if (NumberToPack
>= 0x0000000080000000)
1836 if (NumberToPack
>= 0x0000000000800000)
1838 if (NumberToPack
>= 0x0000000000008000)
1840 if (NumberToPack
>= 0x0000000000000080)
1846 if (NumberToPack
<= 0xff80000000000000)
1848 if (NumberToPack
<= 0xffff800000000000)
1850 if (NumberToPack
<= 0xffffff8000000000)
1852 if (NumberToPack
<= 0xffffffff80000000)
1854 if (NumberToPack
<= 0xffffffffff800000)
1856 if (NumberToPack
<= 0xffffffffffff8000)
1858 if (NumberToPack
<= 0xffffffffffffff80)
1865 GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb
, PNTFS_ATTR_RECORD Attribute
, PULONGLONG LastCluster
)
1867 LONGLONG DataRunOffset
;
1868 ULONGLONG DataRunLength
;
1869 LONGLONG DataRunStartLCN
;
1871 ULONGLONG LastLCN
= 0;
1872 PUCHAR DataRun
= (PUCHAR
)Attribute
+ Attribute
->NonResident
.MappingPairsOffset
;
1874 if (!Attribute
->IsNonResident
)
1875 return STATUS_INVALID_PARAMETER
;
1879 DataRun
= DecodeRun(DataRun
, &DataRunOffset
, &DataRunLength
);
1881 if (DataRunOffset
!= -1)
1884 DataRunStartLCN
= LastLCN
+ DataRunOffset
;
1885 LastLCN
= DataRunStartLCN
;
1886 *LastCluster
= LastLCN
+ DataRunLength
- 1;
1893 return STATUS_SUCCESS
;
1896 PSTANDARD_INFORMATION
1897 GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb
,
1898 PFILE_RECORD_HEADER FileRecord
)
1901 FIND_ATTR_CONTXT Context
;
1902 PNTFS_ATTR_RECORD Attribute
;
1903 PSTANDARD_INFORMATION StdInfo
;
1905 Status
= FindFirstAttribute(&Context
, Vcb
, FileRecord
, FALSE
, &Attribute
);
1906 while (NT_SUCCESS(Status
))
1908 if (Attribute
->Type
== AttributeStandardInformation
)
1910 StdInfo
= (PSTANDARD_INFORMATION
)((ULONG_PTR
)Attribute
+ Attribute
->Resident
.ValueOffset
);
1911 FindCloseAttribute(&Context
);
1915 Status
= FindNextAttribute(&Context
, &Attribute
);
1918 FindCloseAttribute(&Context
);
1923 * @name GetFileNameAttributeLength
1926 * Returns the size of a given FILENAME_ATTRIBUTE, in bytes.
1928 * @param FileNameAttribute
1929 * Pointer to a FILENAME_ATTRIBUTE to determine the size of.
1932 * The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end.
1933 * This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD.
1935 ULONG
GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute
)
1937 ULONG Length
= FIELD_OFFSET(FILENAME_ATTRIBUTE
, Name
) + (FileNameAttribute
->NameLength
* sizeof(WCHAR
));
1942 GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb
,
1943 PFILE_RECORD_HEADER FileRecord
)
1945 PFILENAME_ATTRIBUTE FileName
;
1947 FileName
= GetFileNameFromRecord(Vcb
, FileRecord
, NTFS_FILE_NAME_POSIX
);
1948 if (FileName
== NULL
)
1950 FileName
= GetFileNameFromRecord(Vcb
, FileRecord
, NTFS_FILE_NAME_WIN32
);
1951 if (FileName
== NULL
)
1953 FileName
= GetFileNameFromRecord(Vcb
, FileRecord
, NTFS_FILE_NAME_DOS
);