e6d3483553b1fcaf9cfd3fa44e1d5ca625c83698
[reactos.git] / drivers / filesystems / ntfs / attrib.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 2002,2003 ReactOS Team
4 *
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.
9 *
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.
14 *
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.
18 *
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
24 * Valentin Verkhovsky
25 * Hervé Poussineau (hpoussin@reactos.org)
26 * Pierre Schweitzer (pierre@reactos.org)
27 */
28
29 /* INCLUDES *****************************************************************/
30
31 #include "ntfs.h"
32 #include <ntintsafe.h>
33
34 #define NDEBUG
35 #include <debug.h>
36
37 /* FUNCTIONS ****************************************************************/
38
39 /**
40 * @name AddData
41 * @implemented
42 *
43 * Adds a $DATA attribute to a given FileRecord.
44 *
45 * @param FileRecord
46 * Pointer to a complete file record to add the attribute to. Caller is responsible for
47 * ensuring FileRecord is large enough to contain $DATA.
48 *
49 * @param AttributeAddress
50 * Pointer to the region of memory that will receive the $DATA attribute.
51 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
52 *
53 * @return
54 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
55 * of the given file record.
56 *
57 * @remarks
58 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
59 * be of type AttributeEnd.
60 * As it's implemented, this function is only intended to assist in creating new file records. It
61 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
62 * It's the caller's responsibility to ensure the given file record has enough memory allocated
63 * for the attribute.
64 */
65 NTSTATUS
66 AddData(PFILE_RECORD_HEADER FileRecord,
67 PNTFS_ATTR_RECORD AttributeAddress)
68 {
69 ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
70 ULONG FileRecordEnd = AttributeAddress->Length;
71
72 if (AttributeAddress->Type != AttributeEnd)
73 {
74 DPRINT1("FIXME: Can only add $DATA attribute to the end of a file record.\n");
75 return STATUS_NOT_IMPLEMENTED;
76 }
77
78 AttributeAddress->Type = AttributeData;
79 AttributeAddress->Length = ResidentHeaderLength;
80 AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
81 AttributeAddress->Resident.ValueLength = 0;
82 AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
83
84 // for unnamed $DATA attributes, NameOffset equals header length
85 AttributeAddress->NameOffset = ResidentHeaderLength;
86 AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
87
88 // move the attribute-end and file-record-end markers to the end of the file record
89 AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
90 SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
91
92 return STATUS_SUCCESS;
93 }
94
95 /**
96 * @name AddFileName
97 * @implemented
98 *
99 * Adds a $FILE_NAME attribute to a given FileRecord.
100 *
101 * @param FileRecord
102 * Pointer to a complete file record to add the attribute to. Caller is responsible for
103 * ensuring FileRecord is large enough to contain $FILE_NAME.
104 *
105 * @param AttributeAddress
106 * Pointer to the region of memory that will receive the $FILE_NAME attribute.
107 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
108 *
109 * @param DeviceExt
110 * Points to the target disk's DEVICE_EXTENSION.
111 *
112 * @param FileObject
113 * Pointer to the FILE_OBJECT which represents the new name.
114 * This parameter is used to determine the filename and parent directory.
115 *
116 * @param CaseSensitive
117 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
118 * if an application opened the file with the FILE_FLAG_POSIX_SEMANTICS flag.
119 *
120 * @param ParentMftIndex
121 * Pointer to a ULONGLONG which will receive the index of the parent directory.
122 *
123 * @return
124 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
125 * of the given file record.
126 *
127 * @remarks
128 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
129 * be of type AttributeEnd.
130 * As it's implemented, this function is only intended to assist in creating new file records. It
131 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
132 * It's the caller's responsibility to ensure the given file record has enough memory allocated
133 * for the attribute.
134 */
135 NTSTATUS
136 AddFileName(PFILE_RECORD_HEADER FileRecord,
137 PNTFS_ATTR_RECORD AttributeAddress,
138 PDEVICE_EXTENSION DeviceExt,
139 PFILE_OBJECT FileObject,
140 BOOLEAN CaseSensitive,
141 PULONGLONG ParentMftIndex)
142 {
143 ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
144 PFILENAME_ATTRIBUTE FileNameAttribute;
145 LARGE_INTEGER SystemTime;
146 ULONG FileRecordEnd = AttributeAddress->Length;
147 ULONGLONG CurrentMFTIndex = NTFS_FILE_ROOT;
148 UNICODE_STRING Current, Remaining, FilenameNoPath;
149 NTSTATUS Status = STATUS_SUCCESS;
150 ULONG FirstEntry;
151
152 if (AttributeAddress->Type != AttributeEnd)
153 {
154 DPRINT1("FIXME: Can only add $FILE_NAME attribute to the end of a file record.\n");
155 return STATUS_NOT_IMPLEMENTED;
156 }
157
158 AttributeAddress->Type = AttributeFileName;
159 AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
160
161 FileNameAttribute = (PFILENAME_ATTRIBUTE)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
162
163 // set timestamps
164 KeQuerySystemTime(&SystemTime);
165 FileNameAttribute->CreationTime = SystemTime.QuadPart;
166 FileNameAttribute->ChangeTime = SystemTime.QuadPart;
167 FileNameAttribute->LastWriteTime = SystemTime.QuadPart;
168 FileNameAttribute->LastAccessTime = SystemTime.QuadPart;
169
170 FileNameAttribute->FileAttributes = NTFS_FILE_TYPE_ARCHIVE;
171
172 // we need to extract the filename from the path
173 DPRINT1("Pathname: %wZ\n", &FileObject->FileName);
174
175 FilenameNoPath.Buffer = FileObject->FileName.Buffer;
176 FilenameNoPath.MaximumLength = FilenameNoPath.Length = FileObject->FileName.Length;
177
178 FsRtlDissectName(FileObject->FileName, &Current, &Remaining);
179
180 while (Current.Length != 0)
181 {
182 DPRINT1("Current: %wZ\n", &Current);
183
184 if (Remaining.Length != 0)
185 {
186 FilenameNoPath.Buffer = Remaining.Buffer;
187 FilenameNoPath.Length = FilenameNoPath.MaximumLength = Remaining.Length;
188 }
189
190 FirstEntry = 0;
191 Status = NtfsFindMftRecord(DeviceExt,
192 CurrentMFTIndex,
193 &Current,
194 &FirstEntry,
195 FALSE,
196 CaseSensitive,
197 &CurrentMFTIndex);
198 if (!NT_SUCCESS(Status))
199 break;
200
201 if (Remaining.Length == 0 )
202 {
203 if (Current.Length != 0)
204 {
205 FilenameNoPath.Buffer = Current.Buffer;
206 FilenameNoPath.Length = FilenameNoPath.MaximumLength = Current.Length;
207 }
208 break;
209 }
210
211 FsRtlDissectName(Remaining, &Current, &Remaining);
212 }
213
214 DPRINT1("MFT Index of parent: %I64u\n", CurrentMFTIndex);
215
216 // set reference to parent directory
217 FileNameAttribute->DirectoryFileReferenceNumber = CurrentMFTIndex;
218 *ParentMftIndex = CurrentMFTIndex;
219
220 DPRINT1("SequenceNumber: 0x%02x\n", FileRecord->SequenceNumber);
221
222 // The highest 2 bytes should be the sequence number, unless the parent happens to be root
223 if (CurrentMFTIndex == NTFS_FILE_ROOT)
224 FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)NTFS_FILE_ROOT << 48;
225 else
226 FileNameAttribute->DirectoryFileReferenceNumber |= (ULONGLONG)FileRecord->SequenceNumber << 48;
227
228 DPRINT1("FileNameAttribute->DirectoryFileReferenceNumber: 0x%016I64x\n", FileNameAttribute->DirectoryFileReferenceNumber);
229
230 FileNameAttribute->NameLength = FilenameNoPath.Length / sizeof(WCHAR);
231 RtlCopyMemory(FileNameAttribute->Name, FilenameNoPath.Buffer, FilenameNoPath.Length);
232
233 // For now, we're emulating the way Windows behaves when 8.3 name generation is disabled
234 // TODO: add DOS Filename as needed
235 if (RtlIsNameLegalDOS8Dot3(&FilenameNoPath, NULL, NULL))
236 FileNameAttribute->NameType = NTFS_FILE_NAME_WIN32_AND_DOS;
237 else
238 FileNameAttribute->NameType = NTFS_FILE_NAME_POSIX;
239
240 FileRecord->LinkCount++;
241
242 AttributeAddress->Length = ResidentHeaderLength +
243 FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
244 AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
245
246 AttributeAddress->Resident.ValueLength = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + FilenameNoPath.Length;
247 AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
248 AttributeAddress->Resident.Flags = RA_INDEXED;
249
250 // move the attribute-end and file-record-end markers to the end of the file record
251 AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
252 SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
253
254 return Status;
255 }
256
257 /**
258 * @name AddRun
259 * @implemented
260 *
261 * Adds a run of allocated clusters to a non-resident attribute.
262 *
263 * @param Vcb
264 * Pointer to an NTFS_VCB for the destination volume.
265 *
266 * @param AttrContext
267 * Pointer to an NTFS_ATTR_CONTEXT describing the destination attribute.
268 *
269 * @param AttrOffset
270 * Byte offset of the destination attribute relative to its file record.
271 *
272 * @param FileRecord
273 * Pointer to a complete copy of the file record containing the destination attribute. Must be at least
274 * Vcb->NtfsInfo.BytesPerFileRecord bytes long.
275 *
276 * @param NextAssignedCluster
277 * Logical cluster number of the start of the data run being added.
278 *
279 * @param RunLength
280 * How many clusters are in the data run being added. Can't be 0.
281 *
282 * @return
283 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute.
284 * STATUS_INSUFFICIENT_RESOURCES if ConvertDataRunsToLargeMCB() fails or if we fail to allocate a
285 * buffer for the new data runs.
286 * STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if FsRtlAddLargeMcbEntry() fails.
287 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
288 * STATUS_NOT_IMPLEMENTED if we need to migrate the attribute to an attribute list (TODO).
289 *
290 * @remarks
291 * Clusters should have been allocated previously with NtfsAllocateClusters().
292 *
293 *
294 */
295 NTSTATUS
296 AddRun(PNTFS_VCB Vcb,
297 PNTFS_ATTR_CONTEXT AttrContext,
298 ULONG AttrOffset,
299 PFILE_RECORD_HEADER FileRecord,
300 ULONGLONG NextAssignedCluster,
301 ULONG RunLength)
302 {
303 NTSTATUS Status;
304 int DataRunMaxLength;
305 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
306 ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
307 ULONGLONG NextVBN = 0;
308
309 PUCHAR RunBuffer;
310 ULONG RunBufferSize;
311
312 if (!AttrContext->pRecord->IsNonResident)
313 return STATUS_INVALID_PARAMETER;
314
315 if (AttrContext->pRecord->NonResident.AllocatedSize != 0)
316 NextVBN = AttrContext->pRecord->NonResident.HighestVCN + 1;
317
318 // Add newly-assigned clusters to mcb
319 _SEH2_TRY
320 {
321 if (!FsRtlAddLargeMcbEntry(&AttrContext->DataRunsMCB,
322 NextVBN,
323 NextAssignedCluster,
324 RunLength))
325 {
326 ExRaiseStatus(STATUS_UNSUCCESSFUL);
327 }
328 }
329 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
330 {
331 DPRINT1("Failed to add LargeMcb Entry!\n");
332 _SEH2_YIELD(return _SEH2_GetExceptionCode());
333 }
334 _SEH2_END;
335
336 RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
337 if (!RunBuffer)
338 {
339 DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
340 return STATUS_INSUFFICIENT_RESOURCES;
341 }
342
343 // Convert the map control block back to encoded data runs
344 ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
345
346 // Get the amount of free space between the start of the of the first data run and the attribute end
347 DataRunMaxLength = AttrContext->pRecord->Length - AttrContext->pRecord->NonResident.MappingPairsOffset;
348
349 // Do we need to extend the attribute (or convert to attribute list)?
350 if (DataRunMaxLength < RunBufferSize)
351 {
352 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
353 PNTFS_ATTR_RECORD NewRecord;
354
355 // Add free space at the end of the file record to DataRunMaxLength
356 DataRunMaxLength += Vcb->NtfsInfo.BytesPerFileRecord - FileRecord->BytesInUse;
357
358 // Can we resize the attribute?
359 if (DataRunMaxLength < RunBufferSize)
360 {
361 DPRINT1("FIXME: Need to create attribute list! Max Data Run Length available: %d, RunBufferSize: %d\n", DataRunMaxLength, RunBufferSize);
362 ExFreePoolWithTag(RunBuffer, TAG_NTFS);
363 return STATUS_NOT_IMPLEMENTED;
364 }
365
366 // Are there more attributes after the one we're resizing?
367 if (NextAttribute->Type != AttributeEnd)
368 {
369 PNTFS_ATTR_RECORD FinalAttribute;
370
371 // Calculate where to move the trailing attributes
372 ULONG_PTR MoveTo = (ULONG_PTR)DestinationAttribute + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize;
373 MoveTo = ALIGN_UP_BY(MoveTo, ATTR_RECORD_ALIGNMENT);
374
375 DPRINT1("Moving attribute(s) after this one starting with type 0x%lx\n", NextAttribute->Type);
376
377 // Move the trailing attributes; FinalAttribute will point to the end marker
378 FinalAttribute = MoveAttributes(Vcb, NextAttribute, NextAttributeOffset, MoveTo);
379
380 // set the file record end
381 SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END);
382 }
383
384 // calculate position of end markers
385 NextAttributeOffset = AttrOffset + AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize;
386 NextAttributeOffset = ALIGN_UP_BY(NextAttributeOffset, ATTR_RECORD_ALIGNMENT);
387
388 // Update the length of the destination attribute
389 DestinationAttribute->Length = NextAttributeOffset - AttrOffset;
390
391 // Create a new copy of the attribute record
392 NewRecord = ExAllocatePoolWithTag(NonPagedPool, DestinationAttribute->Length, TAG_NTFS);
393 RtlCopyMemory(NewRecord, AttrContext->pRecord, AttrContext->pRecord->Length);
394 NewRecord->Length = DestinationAttribute->Length;
395
396 // Free the old copy of the attribute record, which won't be large enough
397 ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
398
399 // Set the attribute context's record to the new copy
400 AttrContext->pRecord = NewRecord;
401
402 // if NextAttribute is the AttributeEnd marker
403 if (NextAttribute->Type == AttributeEnd)
404 {
405 // End the file record
406 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
407 SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
408 }
409 }
410
411 // Update HighestVCN
412 DestinationAttribute->NonResident.HighestVCN =
413 AttrContext->pRecord->NonResident.HighestVCN = max(NextVBN - 1 + RunLength,
414 AttrContext->pRecord->NonResident.HighestVCN);
415
416 // Write data runs to destination attribute
417 RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
418 RunBuffer,
419 RunBufferSize);
420
421 // Update the attribute record in the attribute context
422 RtlCopyMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + AttrContext->pRecord->NonResident.MappingPairsOffset),
423 RunBuffer,
424 RunBufferSize);
425
426 // Update the file record
427 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
428
429 ExFreePoolWithTag(RunBuffer, TAG_NTFS);
430
431 NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
432
433 return Status;
434 }
435
436 /**
437 * @name AddStandardInformation
438 * @implemented
439 *
440 * Adds a $STANDARD_INFORMATION attribute to a given FileRecord.
441 *
442 * @param FileRecord
443 * Pointer to a complete file record to add the attribute to. Caller is responsible for
444 * ensuring FileRecord is large enough to contain $STANDARD_INFORMATION.
445 *
446 * @param AttributeAddress
447 * Pointer to the region of memory that will receive the $STANDARD_INFORMATION attribute.
448 * This address must reside within FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
449 *
450 * @return
451 * STATUS_SUCCESS on success. STATUS_NOT_IMPLEMENTED if target address isn't at the end
452 * of the given file record.
453 *
454 * @remarks
455 * Only adding the attribute to the end of the file record is supported; AttributeAddress must
456 * be of type AttributeEnd.
457 * As it's implemented, this function is only intended to assist in creating new file records. It
458 * could be made more general-purpose by considering file records with an $ATTRIBUTE_LIST.
459 * It's the caller's responsibility to ensure the given file record has enough memory allocated
460 * for the attribute.
461 */
462 NTSTATUS
463 AddStandardInformation(PFILE_RECORD_HEADER FileRecord,
464 PNTFS_ATTR_RECORD AttributeAddress)
465 {
466 ULONG ResidentHeaderLength = FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.Reserved) + sizeof(UCHAR);
467 PSTANDARD_INFORMATION StandardInfo = (PSTANDARD_INFORMATION)((LONG_PTR)AttributeAddress + ResidentHeaderLength);
468 LARGE_INTEGER SystemTime;
469 ULONG FileRecordEnd = AttributeAddress->Length;
470
471 if (AttributeAddress->Type != AttributeEnd)
472 {
473 DPRINT1("FIXME: Can only add $STANDARD_INFORMATION attribute to the end of a file record.\n");
474 return STATUS_NOT_IMPLEMENTED;
475 }
476
477 AttributeAddress->Type = AttributeStandardInformation;
478 AttributeAddress->Length = sizeof(STANDARD_INFORMATION) + ResidentHeaderLength;
479 AttributeAddress->Length = ALIGN_UP_BY(AttributeAddress->Length, ATTR_RECORD_ALIGNMENT);
480 AttributeAddress->Resident.ValueLength = sizeof(STANDARD_INFORMATION);
481 AttributeAddress->Resident.ValueOffset = ResidentHeaderLength;
482 AttributeAddress->Instance = FileRecord->NextAttributeNumber++;
483
484 // set dates and times
485 KeQuerySystemTime(&SystemTime);
486 StandardInfo->CreationTime = SystemTime.QuadPart;
487 StandardInfo->ChangeTime = SystemTime.QuadPart;
488 StandardInfo->LastWriteTime = SystemTime.QuadPart;
489 StandardInfo->LastAccessTime = SystemTime.QuadPart;
490 StandardInfo->FileAttribute = NTFS_FILE_TYPE_ARCHIVE;
491
492 // move the attribute-end and file-record-end markers to the end of the file record
493 AttributeAddress = (PNTFS_ATTR_RECORD)((ULONG_PTR)AttributeAddress + AttributeAddress->Length);
494 SetFileRecordEnd(FileRecord, AttributeAddress, FileRecordEnd);
495
496 return STATUS_SUCCESS;
497 }
498
499 /**
500 * @name ConvertDataRunsToLargeMCB
501 * @implemented
502 *
503 * Converts binary data runs to a map control block.
504 *
505 * @param DataRun
506 * Pointer to the run data
507 *
508 * @param DataRunsMCB
509 * Pointer to an unitialized LARGE_MCB structure.
510 *
511 * @return
512 * STATUS_SUCCESS on success, STATUS_INSUFFICIENT_RESOURCES or STATUS_UNSUCCESSFUL if we fail to
513 * initialize the mcb or add an entry.
514 *
515 * @remarks
516 * Initializes the LARGE_MCB pointed to by DataRunsMCB. If this function succeeds, you
517 * need to call FsRtlUninitializeLargeMcb() when you're done with DataRunsMCB. This
518 * function will ensure the LargeMCB has been unitialized in case of failure.
519 *
520 */
521 NTSTATUS
522 ConvertDataRunsToLargeMCB(PUCHAR DataRun,
523 PLARGE_MCB DataRunsMCB,
524 PULONGLONG pNextVBN)
525 {
526 LONGLONG DataRunOffset;
527 ULONGLONG DataRunLength;
528 LONGLONG DataRunStartLCN;
529 ULONGLONG LastLCN = 0;
530
531 // Initialize the MCB, potentially catch an exception
532 _SEH2_TRY{
533 FsRtlInitializeLargeMcb(DataRunsMCB, NonPagedPool);
534 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
535 _SEH2_YIELD(return _SEH2_GetExceptionCode());
536 } _SEH2_END;
537
538 while (*DataRun != 0)
539 {
540 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
541
542 if (DataRunOffset != -1)
543 {
544 // Normal data run.
545 DataRunStartLCN = LastLCN + DataRunOffset;
546 LastLCN = DataRunStartLCN;
547
548 _SEH2_TRY{
549 if (!FsRtlAddLargeMcbEntry(DataRunsMCB,
550 *pNextVBN,
551 DataRunStartLCN,
552 DataRunLength))
553 {
554 ExRaiseStatus(STATUS_UNSUCCESSFUL);
555 }
556 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
557 FsRtlUninitializeLargeMcb(DataRunsMCB);
558 _SEH2_YIELD(return _SEH2_GetExceptionCode());
559 } _SEH2_END;
560
561 }
562
563 *pNextVBN += DataRunLength;
564 }
565
566 return STATUS_SUCCESS;
567 }
568
569 /**
570 * @name ConvertLargeMCBToDataRuns
571 * @implemented
572 *
573 * Converts a map control block to a series of encoded data runs (used by non-resident attributes).
574 *
575 * @param DataRunsMCB
576 * Pointer to a LARGE_MCB structure describing the data runs.
577 *
578 * @param RunBuffer
579 * Pointer to the buffer that will receive the encoded data runs.
580 *
581 * @param MaxBufferSize
582 * Size of RunBuffer, in bytes.
583 *
584 * @param UsedBufferSize
585 * Pointer to a ULONG that will receive the size of the data runs in bytes. Can't be NULL.
586 *
587 * @return
588 * STATUS_SUCCESS on success, STATUS_BUFFER_TOO_SMALL if RunBuffer is too small to contain the
589 * complete output.
590 *
591 */
592 NTSTATUS
593 ConvertLargeMCBToDataRuns(PLARGE_MCB DataRunsMCB,
594 PUCHAR RunBuffer,
595 ULONG MaxBufferSize,
596 PULONG UsedBufferSize)
597 {
598 NTSTATUS Status = STATUS_SUCCESS;
599 ULONG RunBufferOffset = 0;
600 LONGLONG DataRunOffset;
601 ULONGLONG LastLCN = 0;
602 LONGLONG Vbn, Lbn, Count;
603 ULONG i;
604
605
606 DPRINT("\t[Vbn, Lbn, Count]\n");
607
608 // convert each mcb entry to a data run
609 for (i = 0; FsRtlGetNextLargeMcbEntry(DataRunsMCB, i, &Vbn, &Lbn, &Count); i++)
610 {
611 UCHAR DataRunOffsetSize = 0;
612 UCHAR DataRunLengthSize = 0;
613 UCHAR ControlByte = 0;
614
615 // [vbn, lbn, count]
616 DPRINT("\t[%I64d, %I64d,%I64d]\n", Vbn, Lbn, Count);
617
618 // TODO: check for holes and convert to sparse runs
619 DataRunOffset = Lbn - LastLCN;
620 LastLCN = Lbn;
621
622 // now we need to determine how to represent DataRunOffset with the minimum number of bytes
623 DPRINT("Determining how many bytes needed to represent %I64x\n", DataRunOffset);
624 DataRunOffsetSize = GetPackedByteCount(DataRunOffset, TRUE);
625 DPRINT("%d bytes needed.\n", DataRunOffsetSize);
626
627 // determine how to represent DataRunLengthSize with the minimum number of bytes
628 DPRINT("Determining how many bytes needed to represent %I64x\n", Count);
629 DataRunLengthSize = GetPackedByteCount(Count, TRUE);
630 DPRINT("%d bytes needed.\n", DataRunLengthSize);
631
632 // ensure the next data run + end marker would be <= Max buffer size
633 if (RunBufferOffset + 2 + DataRunLengthSize + DataRunOffsetSize > MaxBufferSize)
634 {
635 Status = STATUS_BUFFER_TOO_SMALL;
636 DPRINT1("FIXME: Ran out of room in buffer for data runs!\n");
637 break;
638 }
639
640 // pack and copy the control byte
641 ControlByte = (DataRunOffsetSize << 4) + DataRunLengthSize;
642 RunBuffer[RunBufferOffset++] = ControlByte;
643
644 // copy DataRunLength
645 RtlCopyMemory(RunBuffer + RunBufferOffset, &Count, DataRunLengthSize);
646 RunBufferOffset += DataRunLengthSize;
647
648 // copy DataRunOffset
649 RtlCopyMemory(RunBuffer + RunBufferOffset, &DataRunOffset, DataRunOffsetSize);
650 RunBufferOffset += DataRunOffsetSize;
651 }
652
653 // End of data runs
654 RunBuffer[RunBufferOffset++] = 0;
655
656 *UsedBufferSize = RunBufferOffset;
657 DPRINT("New Size of DataRuns: %ld\n", *UsedBufferSize);
658
659 return Status;
660 }
661
662 PUCHAR
663 DecodeRun(PUCHAR DataRun,
664 LONGLONG *DataRunOffset,
665 ULONGLONG *DataRunLength)
666 {
667 UCHAR DataRunOffsetSize;
668 UCHAR DataRunLengthSize;
669 CHAR i;
670
671 DataRunOffsetSize = (*DataRun >> 4) & 0xF;
672 DataRunLengthSize = *DataRun & 0xF;
673 *DataRunOffset = 0;
674 *DataRunLength = 0;
675 DataRun++;
676 for (i = 0; i < DataRunLengthSize; i++)
677 {
678 *DataRunLength += ((ULONG64)*DataRun) << (i * 8);
679 DataRun++;
680 }
681
682 /* NTFS 3+ sparse files */
683 if (DataRunOffsetSize == 0)
684 {
685 *DataRunOffset = -1;
686 }
687 else
688 {
689 for (i = 0; i < DataRunOffsetSize - 1; i++)
690 {
691 *DataRunOffset += ((ULONG64)*DataRun) << (i * 8);
692 DataRun++;
693 }
694 /* The last byte contains sign so we must process it different way. */
695 *DataRunOffset = ((LONG64)(CHAR)(*(DataRun++)) << (i * 8)) + *DataRunOffset;
696 }
697
698 DPRINT("DataRunOffsetSize: %x\n", DataRunOffsetSize);
699 DPRINT("DataRunLengthSize: %x\n", DataRunLengthSize);
700 DPRINT("DataRunOffset: %x\n", *DataRunOffset);
701 DPRINT("DataRunLength: %x\n", *DataRunLength);
702
703 return DataRun;
704 }
705
706 BOOLEAN
707 FindRun(PNTFS_ATTR_RECORD NresAttr,
708 ULONGLONG vcn,
709 PULONGLONG lcn,
710 PULONGLONG count)
711 {
712 if (vcn < NresAttr->NonResident.LowestVCN || vcn > NresAttr->NonResident.HighestVCN)
713 return FALSE;
714
715 DecodeRun((PUCHAR)((ULONG_PTR)NresAttr + NresAttr->NonResident.MappingPairsOffset), (PLONGLONG)lcn, count);
716
717 return TRUE;
718 }
719
720 /**
721 * @name FreeClusters
722 * @implemented
723 *
724 * Shrinks the allocation size of a non-resident attribute by a given number of clusters.
725 * Frees the clusters from the volume's $BITMAP file as well as the attribute's data runs.
726 *
727 * @param Vcb
728 * Pointer to an NTFS_VCB for the destination volume.
729 *
730 * @param AttrContext
731 * Pointer to an NTFS_ATTR_CONTEXT describing the attribute from which the clusters will be freed.
732 *
733 * @param AttrOffset
734 * Byte offset of the destination attribute relative to its file record.
735 *
736 * @param FileRecord
737 * Pointer to a complete copy of the file record containing the attribute. Must be at least
738 * Vcb->NtfsInfo.BytesPerFileRecord bytes long.
739 *
740 * @param ClustersToFree
741 * Number of clusters that should be freed from the end of the data stream. Must be no more
742 * Than the number of clusters assigned to the attribute (HighestVCN + 1).
743 *
744 * @return
745 * STATUS_SUCCESS on success. STATUS_INVALID_PARAMETER if AttrContext describes a resident attribute,
746 * or if the caller requested more clusters be freed than the attribute has been allocated.
747 * STATUS_INSUFFICIENT_RESOURCES if allocating a buffer for the data runs fails or
748 * if ConvertDataRunsToLargeMCB() fails.
749 * STATUS_BUFFER_TOO_SMALL if ConvertLargeMCBToDataRuns() fails.
750 *
751 *
752 */
753 NTSTATUS
754 FreeClusters(PNTFS_VCB Vcb,
755 PNTFS_ATTR_CONTEXT AttrContext,
756 ULONG AttrOffset,
757 PFILE_RECORD_HEADER FileRecord,
758 ULONG ClustersToFree)
759 {
760 NTSTATUS Status = STATUS_SUCCESS;
761 ULONG ClustersLeftToFree = ClustersToFree;
762
763 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
764 ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
765 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + NextAttributeOffset);
766
767 PUCHAR RunBuffer;
768 ULONG RunBufferSize = 0;
769
770 PFILE_RECORD_HEADER BitmapRecord;
771 PNTFS_ATTR_CONTEXT DataContext;
772 ULONGLONG BitmapDataSize;
773 PUCHAR BitmapData;
774 RTL_BITMAP Bitmap;
775 ULONG LengthWritten;
776
777 if (!AttrContext->pRecord->IsNonResident)
778 {
779 return STATUS_INVALID_PARAMETER;
780 }
781
782 // Read the $Bitmap file
783 BitmapRecord = ExAllocatePoolWithTag(NonPagedPool,
784 Vcb->NtfsInfo.BytesPerFileRecord,
785 TAG_NTFS);
786 if (BitmapRecord == NULL)
787 {
788 DPRINT1("Error: Unable to allocate memory for bitmap file record!\n");
789 return STATUS_NO_MEMORY;
790 }
791
792 Status = ReadFileRecord(Vcb, NTFS_FILE_BITMAP, BitmapRecord);
793 if (!NT_SUCCESS(Status))
794 {
795 DPRINT1("Error: Unable to read file record for bitmap!\n");
796 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
797 return 0;
798 }
799
800 Status = FindAttribute(Vcb, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL);
801 if (!NT_SUCCESS(Status))
802 {
803 DPRINT1("Error: Unable to find data attribute for bitmap file!\n");
804 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
805 return 0;
806 }
807
808 BitmapDataSize = AttributeDataLength(DataContext->pRecord);
809 BitmapDataSize = min(BitmapDataSize, ULONG_MAX);
810 ASSERT((BitmapDataSize * 8) >= Vcb->NtfsInfo.ClusterCount);
811 BitmapData = ExAllocatePoolWithTag(NonPagedPool, ROUND_UP(BitmapDataSize, Vcb->NtfsInfo.BytesPerSector), TAG_NTFS);
812 if (BitmapData == NULL)
813 {
814 DPRINT1("Error: Unable to allocate memory for bitmap file data!\n");
815 ReleaseAttributeContext(DataContext);
816 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
817 return 0;
818 }
819
820 ReadAttribute(Vcb, DataContext, 0, (PCHAR)BitmapData, (ULONG)BitmapDataSize);
821
822 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, Vcb->NtfsInfo.ClusterCount);
823
824 // free clusters in $BITMAP file
825 while (ClustersLeftToFree > 0)
826 {
827 LONGLONG LargeVbn, LargeLbn;
828
829 if (!FsRtlLookupLastLargeMcbEntry(&AttrContext->DataRunsMCB, &LargeVbn, &LargeLbn))
830 {
831 Status = STATUS_INVALID_PARAMETER;
832 DPRINT1("DRIVER ERROR: FreeClusters called to free %lu clusters, which is %lu more clusters than are assigned to attribute!",
833 ClustersToFree,
834 ClustersLeftToFree);
835 break;
836 }
837
838 if (LargeLbn != -1)
839 {
840 // deallocate this cluster
841 RtlClearBits(&Bitmap, LargeLbn, 1);
842 }
843 FsRtlTruncateLargeMcb(&AttrContext->DataRunsMCB, AttrContext->pRecord->NonResident.HighestVCN);
844
845 // decrement HighestVCN, but don't let it go below 0
846 AttrContext->pRecord->NonResident.HighestVCN = min(AttrContext->pRecord->NonResident.HighestVCN, AttrContext->pRecord->NonResident.HighestVCN - 1);
847 ClustersLeftToFree--;
848 }
849
850 // update $BITMAP file on disk
851 Status = WriteAttribute(Vcb, DataContext, 0, BitmapData, (ULONG)BitmapDataSize, &LengthWritten, FileRecord);
852 if (!NT_SUCCESS(Status))
853 {
854 ReleaseAttributeContext(DataContext);
855 ExFreePoolWithTag(BitmapData, TAG_NTFS);
856 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
857 return Status;
858 }
859
860 ReleaseAttributeContext(DataContext);
861 ExFreePoolWithTag(BitmapData, TAG_NTFS);
862 ExFreePoolWithTag(BitmapRecord, TAG_NTFS);
863
864 // Save updated data runs to file record
865
866 // Allocate some memory for a new RunBuffer
867 RunBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
868 if (!RunBuffer)
869 {
870 DPRINT1("ERROR: Couldn't allocate memory for data runs!\n");
871 return STATUS_INSUFFICIENT_RESOURCES;
872 }
873
874 // Convert the map control block back to encoded data runs
875 ConvertLargeMCBToDataRuns(&AttrContext->DataRunsMCB, RunBuffer, Vcb->NtfsInfo.BytesPerCluster, &RunBufferSize);
876
877 // Update HighestVCN
878 DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN;
879
880 // Write data runs to destination attribute
881 RtlCopyMemory((PVOID)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset),
882 RunBuffer,
883 RunBufferSize);
884
885 // Is DestinationAttribute the last attribute in the file record?
886 if (NextAttribute->Type == AttributeEnd)
887 {
888 // update attribute length
889 DestinationAttribute->Length = ALIGN_UP_BY(AttrContext->pRecord->NonResident.MappingPairsOffset + RunBufferSize,
890 ATTR_RECORD_ALIGNMENT);
891
892 ASSERT(DestinationAttribute->Length <= AttrContext->pRecord->Length);
893
894 AttrContext->pRecord->Length = DestinationAttribute->Length;
895
896 // write end markers
897 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
898 SetFileRecordEnd(FileRecord, NextAttribute, FILE_RECORD_END);
899 }
900
901 // Update the file record
902 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
903
904 ExFreePoolWithTag(RunBuffer, TAG_NTFS);
905
906 NtfsDumpDataRuns((PUCHAR)((ULONG_PTR)DestinationAttribute + DestinationAttribute->NonResident.MappingPairsOffset), 0);
907
908 return Status;
909 }
910
911 static
912 NTSTATUS
913 InternalReadNonResidentAttributes(PFIND_ATTR_CONTXT Context)
914 {
915 ULONGLONG ListSize;
916 PNTFS_ATTR_RECORD Attribute;
917 PNTFS_ATTR_CONTEXT ListContext;
918
919 DPRINT("InternalReadNonResidentAttributes(%p)\n", Context);
920
921 Attribute = Context->CurrAttr;
922 ASSERT(Attribute->Type == AttributeAttributeList);
923
924 if (Context->OnlyResident)
925 {
926 Context->NonResidentStart = NULL;
927 Context->NonResidentEnd = NULL;
928 return STATUS_SUCCESS;
929 }
930
931 if (Context->NonResidentStart != NULL)
932 {
933 return STATUS_FILE_CORRUPT_ERROR;
934 }
935
936 ListContext = PrepareAttributeContext(Attribute);
937 ListSize = AttributeDataLength(ListContext->pRecord);
938 if (ListSize > 0xFFFFFFFF)
939 {
940 ReleaseAttributeContext(ListContext);
941 return STATUS_BUFFER_OVERFLOW;
942 }
943
944 Context->NonResidentStart = ExAllocatePoolWithTag(NonPagedPool, (ULONG)ListSize, TAG_NTFS);
945 if (Context->NonResidentStart == NULL)
946 {
947 ReleaseAttributeContext(ListContext);
948 return STATUS_INSUFFICIENT_RESOURCES;
949 }
950
951 if (ReadAttribute(Context->Vcb, ListContext, 0, (PCHAR)Context->NonResidentStart, (ULONG)ListSize) != ListSize)
952 {
953 ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
954 Context->NonResidentStart = NULL;
955 ReleaseAttributeContext(ListContext);
956 return STATUS_FILE_CORRUPT_ERROR;
957 }
958
959 ReleaseAttributeContext(ListContext);
960 Context->NonResidentEnd = (PNTFS_ATTR_RECORD)((PCHAR)Context->NonResidentStart + ListSize);
961 return STATUS_SUCCESS;
962 }
963
964 static
965 PNTFS_ATTR_RECORD
966 InternalGetNextAttribute(PFIND_ATTR_CONTXT Context)
967 {
968 PNTFS_ATTR_RECORD NextAttribute;
969
970 if (Context->CurrAttr == (PVOID)-1)
971 {
972 return NULL;
973 }
974
975 if (Context->CurrAttr >= Context->FirstAttr &&
976 Context->CurrAttr < Context->LastAttr)
977 {
978 if (Context->CurrAttr->Length == 0)
979 {
980 DPRINT1("Broken length!\n");
981 Context->CurrAttr = (PVOID)-1;
982 return NULL;
983 }
984
985 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
986
987 if (NextAttribute > Context->LastAttr || NextAttribute < Context->FirstAttr)
988 {
989 DPRINT1("Broken length: 0x%lx!\n", Context->CurrAttr->Length);
990 Context->CurrAttr = (PVOID)-1;
991 return NULL;
992 }
993
994 Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
995 Context->CurrAttr = NextAttribute;
996
997 if (Context->CurrAttr < Context->LastAttr &&
998 Context->CurrAttr->Type != AttributeEnd)
999 {
1000 return Context->CurrAttr;
1001 }
1002 }
1003
1004 if (Context->NonResidentStart == NULL)
1005 {
1006 Context->CurrAttr = (PVOID)-1;
1007 return NULL;
1008 }
1009
1010 if (Context->CurrAttr < Context->NonResidentStart ||
1011 Context->CurrAttr >= Context->NonResidentEnd)
1012 {
1013 Context->CurrAttr = Context->NonResidentStart;
1014 }
1015 else if (Context->CurrAttr->Length != 0)
1016 {
1017 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Context->CurrAttr + Context->CurrAttr->Length);
1018 Context->Offset += ((ULONG_PTR)NextAttribute - (ULONG_PTR)Context->CurrAttr);
1019 Context->CurrAttr = NextAttribute;
1020 }
1021 else
1022 {
1023 DPRINT1("Broken length!\n");
1024 Context->CurrAttr = (PVOID)-1;
1025 return NULL;
1026 }
1027
1028 if (Context->CurrAttr < Context->NonResidentEnd &&
1029 Context->CurrAttr->Type != AttributeEnd)
1030 {
1031 return Context->CurrAttr;
1032 }
1033
1034 Context->CurrAttr = (PVOID)-1;
1035 return NULL;
1036 }
1037
1038 NTSTATUS
1039 FindFirstAttribute(PFIND_ATTR_CONTXT Context,
1040 PDEVICE_EXTENSION Vcb,
1041 PFILE_RECORD_HEADER FileRecord,
1042 BOOLEAN OnlyResident,
1043 PNTFS_ATTR_RECORD * Attribute)
1044 {
1045 NTSTATUS Status;
1046
1047 DPRINT("FindFistAttribute(%p, %p, %p, %p, %u, %p)\n", Context, Vcb, FileRecord, OnlyResident, Attribute);
1048
1049 Context->Vcb = Vcb;
1050 Context->OnlyResident = OnlyResident;
1051 Context->FirstAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->AttributeOffset);
1052 Context->CurrAttr = Context->FirstAttr;
1053 Context->LastAttr = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + FileRecord->BytesInUse);
1054 Context->NonResidentStart = NULL;
1055 Context->NonResidentEnd = NULL;
1056 Context->Offset = FileRecord->AttributeOffset;
1057
1058 if (Context->FirstAttr->Type == AttributeEnd)
1059 {
1060 Context->CurrAttr = (PVOID)-1;
1061 return STATUS_END_OF_FILE;
1062 }
1063 else if (Context->FirstAttr->Type == AttributeAttributeList)
1064 {
1065 Status = InternalReadNonResidentAttributes(Context);
1066 if (!NT_SUCCESS(Status))
1067 {
1068 return Status;
1069 }
1070
1071 *Attribute = InternalGetNextAttribute(Context);
1072 if (*Attribute == NULL)
1073 {
1074 return STATUS_END_OF_FILE;
1075 }
1076 }
1077 else
1078 {
1079 *Attribute = Context->CurrAttr;
1080 Context->Offset = (UCHAR*)Context->CurrAttr - (UCHAR*)FileRecord;
1081 }
1082
1083 return STATUS_SUCCESS;
1084 }
1085
1086 NTSTATUS
1087 FindNextAttribute(PFIND_ATTR_CONTXT Context,
1088 PNTFS_ATTR_RECORD * Attribute)
1089 {
1090 NTSTATUS Status;
1091
1092 DPRINT("FindNextAttribute(%p, %p)\n", Context, Attribute);
1093
1094 *Attribute = InternalGetNextAttribute(Context);
1095 if (*Attribute == NULL)
1096 {
1097 return STATUS_END_OF_FILE;
1098 }
1099
1100 if (Context->CurrAttr->Type != AttributeAttributeList)
1101 {
1102 return STATUS_SUCCESS;
1103 }
1104
1105 Status = InternalReadNonResidentAttributes(Context);
1106 if (!NT_SUCCESS(Status))
1107 {
1108 return Status;
1109 }
1110
1111 *Attribute = InternalGetNextAttribute(Context);
1112 if (*Attribute == NULL)
1113 {
1114 return STATUS_END_OF_FILE;
1115 }
1116
1117 return STATUS_SUCCESS;
1118 }
1119
1120 VOID
1121 FindCloseAttribute(PFIND_ATTR_CONTXT Context)
1122 {
1123 if (Context->NonResidentStart != NULL)
1124 {
1125 ExFreePoolWithTag(Context->NonResidentStart, TAG_NTFS);
1126 Context->NonResidentStart = NULL;
1127 }
1128 }
1129
1130 static
1131 VOID
1132 NtfsDumpFileNameAttribute(PNTFS_ATTR_RECORD Attribute)
1133 {
1134 PFILENAME_ATTRIBUTE FileNameAttr;
1135
1136 DbgPrint(" $FILE_NAME ");
1137
1138 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1139
1140 FileNameAttr = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1141 DbgPrint(" (%x) '%.*S' ", FileNameAttr->NameType, FileNameAttr->NameLength, FileNameAttr->Name);
1142 DbgPrint(" '%x' \n", FileNameAttr->FileAttributes);
1143 DbgPrint(" AllocatedSize: %I64u\nDataSize: %I64u\n", FileNameAttr->AllocatedSize, FileNameAttr->DataSize);
1144 DbgPrint(" File reference: 0x%016I64x\n", FileNameAttr->DirectoryFileReferenceNumber);
1145 }
1146
1147
1148 static
1149 VOID
1150 NtfsDumpStandardInformationAttribute(PNTFS_ATTR_RECORD Attribute)
1151 {
1152 PSTANDARD_INFORMATION StandardInfoAttr;
1153
1154 DbgPrint(" $STANDARD_INFORMATION ");
1155
1156 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1157
1158 StandardInfoAttr = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1159 DbgPrint(" '%x' ", StandardInfoAttr->FileAttribute);
1160 }
1161
1162
1163 static
1164 VOID
1165 NtfsDumpVolumeNameAttribute(PNTFS_ATTR_RECORD Attribute)
1166 {
1167 PWCHAR VolumeName;
1168
1169 DbgPrint(" $VOLUME_NAME ");
1170
1171 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1172
1173 VolumeName = (PWCHAR)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1174 DbgPrint(" '%.*S' ", Attribute->Resident.ValueLength / sizeof(WCHAR), VolumeName);
1175 }
1176
1177
1178 static
1179 VOID
1180 NtfsDumpVolumeInformationAttribute(PNTFS_ATTR_RECORD Attribute)
1181 {
1182 PVOLINFO_ATTRIBUTE VolInfoAttr;
1183
1184 DbgPrint(" $VOLUME_INFORMATION ");
1185
1186 // DbgPrint(" Length %lu Offset %hu ", Attribute->Resident.ValueLength, Attribute->Resident.ValueOffset);
1187
1188 VolInfoAttr = (PVOLINFO_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1189 DbgPrint(" NTFS Version %u.%u Flags 0x%04hx ",
1190 VolInfoAttr->MajorVersion,
1191 VolInfoAttr->MinorVersion,
1192 VolInfoAttr->Flags);
1193 }
1194
1195
1196 static
1197 VOID
1198 NtfsDumpIndexRootAttribute(PNTFS_ATTR_RECORD Attribute)
1199 {
1200 PINDEX_ROOT_ATTRIBUTE IndexRootAttr;
1201 ULONG CurrentOffset;
1202 ULONG CurrentNode;
1203
1204 IndexRootAttr = (PINDEX_ROOT_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1205
1206 if (IndexRootAttr->AttributeType == AttributeFileName)
1207 ASSERT(IndexRootAttr->CollationRule == COLLATION_FILE_NAME);
1208
1209 DbgPrint(" $INDEX_ROOT (%u bytes per index record, %u clusters) ", IndexRootAttr->SizeOfEntry, IndexRootAttr->ClustersPerIndexRecord);
1210
1211 if (IndexRootAttr->Header.Flags == INDEX_ROOT_SMALL)
1212 {
1213 DbgPrint(" (small)\n");
1214 }
1215 else
1216 {
1217 ASSERT(IndexRootAttr->Header.Flags == INDEX_ROOT_LARGE);
1218 DbgPrint(" (large)\n");
1219 }
1220
1221 DbgPrint(" Offset to first index: 0x%lx\n Total size of index entries: 0x%lx\n Allocated size of node: 0x%lx\n",
1222 IndexRootAttr->Header.FirstEntryOffset,
1223 IndexRootAttr->Header.TotalSizeOfEntries,
1224 IndexRootAttr->Header.AllocatedSize);
1225 CurrentOffset = IndexRootAttr->Header.FirstEntryOffset;
1226 CurrentNode = 0;
1227 // print details of every node in the index
1228 while (CurrentOffset < IndexRootAttr->Header.TotalSizeOfEntries)
1229 {
1230 PINDEX_ENTRY_ATTRIBUTE currentIndexExtry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRootAttr + 0x10 + CurrentOffset);
1231 DbgPrint(" Index Node Entry %lu", CurrentNode++);
1232 if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_NODE))
1233 DbgPrint(" (Branch)");
1234 else
1235 DbgPrint(" (Leaf)");
1236 if (BooleanFlagOn(currentIndexExtry->Flags, NTFS_INDEX_ENTRY_END))
1237 {
1238 DbgPrint(" (Dummy Key)");
1239 }
1240 DbgPrint("\n File Reference: 0x%016I64x\n", currentIndexExtry->Data.Directory.IndexedFile);
1241 DbgPrint(" Index Entry Length: 0x%x\n", currentIndexExtry->Length);
1242 DbgPrint(" Index Key Length: 0x%x\n", currentIndexExtry->KeyLength);
1243
1244 // if this isn't the final (dummy) node, print info about the key (Filename attribute)
1245 if (!(currentIndexExtry->Flags & NTFS_INDEX_ENTRY_END))
1246 {
1247 UNICODE_STRING Name;
1248 DbgPrint(" Parent File Reference: 0x%016I64x\n", currentIndexExtry->FileName.DirectoryFileReferenceNumber);
1249 DbgPrint(" $FILENAME indexed: ");
1250 Name.Length = currentIndexExtry->FileName.NameLength * sizeof(WCHAR);
1251 Name.MaximumLength = Name.Length;
1252 Name.Buffer = currentIndexExtry->FileName.Name;
1253 DbgPrint("'%wZ'\n", &Name);
1254 }
1255
1256 // if this node has a sub-node beneath it
1257 if (currentIndexExtry->Flags & NTFS_INDEX_ENTRY_NODE)
1258 {
1259 // Print the VCN of the sub-node
1260 PULONGLONG SubNodeVCN = (PULONGLONG)((ULONG_PTR)currentIndexExtry + currentIndexExtry->Length - sizeof(ULONGLONG));
1261 DbgPrint(" VCN of sub-node: 0x%llx\n", *SubNodeVCN);
1262 }
1263
1264 CurrentOffset += currentIndexExtry->Length;
1265 ASSERT(currentIndexExtry->Length);
1266 }
1267
1268 }
1269
1270
1271 static
1272 VOID
1273 NtfsDumpAttribute(PDEVICE_EXTENSION Vcb,
1274 PNTFS_ATTR_RECORD Attribute)
1275 {
1276 UNICODE_STRING Name;
1277
1278 ULONGLONG lcn = 0;
1279 ULONGLONG runcount = 0;
1280
1281 switch (Attribute->Type)
1282 {
1283 case AttributeFileName:
1284 NtfsDumpFileNameAttribute(Attribute);
1285 break;
1286
1287 case AttributeStandardInformation:
1288 NtfsDumpStandardInformationAttribute(Attribute);
1289 break;
1290
1291 case AttributeObjectId:
1292 DbgPrint(" $OBJECT_ID ");
1293 break;
1294
1295 case AttributeSecurityDescriptor:
1296 DbgPrint(" $SECURITY_DESCRIPTOR ");
1297 break;
1298
1299 case AttributeVolumeName:
1300 NtfsDumpVolumeNameAttribute(Attribute);
1301 break;
1302
1303 case AttributeVolumeInformation:
1304 NtfsDumpVolumeInformationAttribute(Attribute);
1305 break;
1306
1307 case AttributeData:
1308 DbgPrint(" $DATA ");
1309 //DataBuf = ExAllocatePool(NonPagedPool,AttributeLengthAllocated(Attribute));
1310 break;
1311
1312 case AttributeIndexRoot:
1313 NtfsDumpIndexRootAttribute(Attribute);
1314 break;
1315
1316 case AttributeIndexAllocation:
1317 DbgPrint(" $INDEX_ALLOCATION ");
1318 break;
1319
1320 case AttributeBitmap:
1321 DbgPrint(" $BITMAP ");
1322 break;
1323
1324 case AttributeReparsePoint:
1325 DbgPrint(" $REPARSE_POINT ");
1326 break;
1327
1328 case AttributeEAInformation:
1329 DbgPrint(" $EA_INFORMATION ");
1330 break;
1331
1332 case AttributeEA:
1333 DbgPrint(" $EA ");
1334 break;
1335
1336 case AttributePropertySet:
1337 DbgPrint(" $PROPERTY_SET ");
1338 break;
1339
1340 case AttributeLoggedUtilityStream:
1341 DbgPrint(" $LOGGED_UTILITY_STREAM ");
1342 break;
1343
1344 default:
1345 DbgPrint(" Attribute %lx ",
1346 Attribute->Type);
1347 break;
1348 }
1349
1350 if (Attribute->Type != AttributeAttributeList)
1351 {
1352 if (Attribute->NameLength != 0)
1353 {
1354 Name.Length = Attribute->NameLength * sizeof(WCHAR);
1355 Name.MaximumLength = Name.Length;
1356 Name.Buffer = (PWCHAR)((ULONG_PTR)Attribute + Attribute->NameOffset);
1357
1358 DbgPrint("'%wZ' ", &Name);
1359 }
1360
1361 DbgPrint("(%s)\n",
1362 Attribute->IsNonResident ? "non-resident" : "resident");
1363
1364 if (Attribute->IsNonResident)
1365 {
1366 FindRun(Attribute,0,&lcn, &runcount);
1367
1368 DbgPrint(" AllocatedSize %I64u DataSize %I64u InitilizedSize %I64u\n",
1369 Attribute->NonResident.AllocatedSize, Attribute->NonResident.DataSize, Attribute->NonResident.InitializedSize);
1370 DbgPrint(" logical clusters: %I64u - %I64u\n",
1371 lcn, lcn + runcount - 1);
1372 }
1373 else
1374 DbgPrint(" %u bytes of data\n", Attribute->Resident.ValueLength);
1375 }
1376 }
1377
1378
1379 VOID NtfsDumpDataRunData(PUCHAR DataRun)
1380 {
1381 UCHAR DataRunOffsetSize;
1382 UCHAR DataRunLengthSize;
1383 CHAR i;
1384
1385 DbgPrint("%02x ", *DataRun);
1386
1387 if (*DataRun == 0)
1388 return;
1389
1390 DataRunOffsetSize = (*DataRun >> 4) & 0xF;
1391 DataRunLengthSize = *DataRun & 0xF;
1392
1393 DataRun++;
1394 for (i = 0; i < DataRunLengthSize; i++)
1395 {
1396 DbgPrint("%02x ", *DataRun);
1397 DataRun++;
1398 }
1399
1400 for (i = 0; i < DataRunOffsetSize; i++)
1401 {
1402 DbgPrint("%02x ", *DataRun);
1403 DataRun++;
1404 }
1405
1406 NtfsDumpDataRunData(DataRun);
1407 }
1408
1409
1410 VOID
1411 NtfsDumpDataRuns(PVOID StartOfRun,
1412 ULONGLONG CurrentLCN)
1413 {
1414 PUCHAR DataRun = StartOfRun;
1415 LONGLONG DataRunOffset;
1416 ULONGLONG DataRunLength;
1417
1418 if (CurrentLCN == 0)
1419 {
1420 DPRINT1("Dumping data runs.\n\tData:\n\t\t");
1421 NtfsDumpDataRunData(StartOfRun);
1422 DbgPrint("\n\tRuns:\n\t\tOff\t\tLCN\t\tLength\n");
1423 }
1424
1425 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1426
1427 if (DataRunOffset != -1)
1428 CurrentLCN += DataRunOffset;
1429
1430 DbgPrint("\t\t%I64d\t", DataRunOffset);
1431 if (DataRunOffset < 99999)
1432 DbgPrint("\t");
1433 DbgPrint("%I64u\t", CurrentLCN);
1434 if (CurrentLCN < 99999)
1435 DbgPrint("\t");
1436 DbgPrint("%I64u\n", DataRunLength);
1437
1438 if (*DataRun == 0)
1439 DbgPrint("\t\t00\n");
1440 else
1441 NtfsDumpDataRuns(DataRun, CurrentLCN);
1442 }
1443
1444
1445 VOID
1446 NtfsDumpFileAttributes(PDEVICE_EXTENSION Vcb,
1447 PFILE_RECORD_HEADER FileRecord)
1448 {
1449 NTSTATUS Status;
1450 FIND_ATTR_CONTXT Context;
1451 PNTFS_ATTR_RECORD Attribute;
1452
1453 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1454 while (NT_SUCCESS(Status))
1455 {
1456 NtfsDumpAttribute(Vcb, Attribute);
1457
1458 Status = FindNextAttribute(&Context, &Attribute);
1459 }
1460
1461 FindCloseAttribute(&Context);
1462 }
1463
1464 PFILENAME_ATTRIBUTE
1465 GetFileNameFromRecord(PDEVICE_EXTENSION Vcb,
1466 PFILE_RECORD_HEADER FileRecord,
1467 UCHAR NameType)
1468 {
1469 FIND_ATTR_CONTXT Context;
1470 PNTFS_ATTR_RECORD Attribute;
1471 PFILENAME_ATTRIBUTE Name;
1472 NTSTATUS Status;
1473
1474 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1475 while (NT_SUCCESS(Status))
1476 {
1477 if (Attribute->Type == AttributeFileName)
1478 {
1479 Name = (PFILENAME_ATTRIBUTE)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1480 if (Name->NameType == NameType ||
1481 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_WIN32) ||
1482 (Name->NameType == NTFS_FILE_NAME_WIN32_AND_DOS && NameType == NTFS_FILE_NAME_DOS))
1483 {
1484 FindCloseAttribute(&Context);
1485 return Name;
1486 }
1487 }
1488
1489 Status = FindNextAttribute(&Context, &Attribute);
1490 }
1491
1492 FindCloseAttribute(&Context);
1493 return NULL;
1494 }
1495
1496 /**
1497 * GetPackedByteCount
1498 * Returns the minimum number of bytes needed to represent the value of a
1499 * 64-bit number. Used to encode data runs.
1500 */
1501 UCHAR
1502 GetPackedByteCount(LONGLONG NumberToPack,
1503 BOOLEAN IsSigned)
1504 {
1505 if (!IsSigned)
1506 {
1507 if (NumberToPack >= 0x0100000000000000)
1508 return 8;
1509 if (NumberToPack >= 0x0001000000000000)
1510 return 7;
1511 if (NumberToPack >= 0x0000010000000000)
1512 return 6;
1513 if (NumberToPack >= 0x0000000100000000)
1514 return 5;
1515 if (NumberToPack >= 0x0000000001000000)
1516 return 4;
1517 if (NumberToPack >= 0x0000000000010000)
1518 return 3;
1519 if (NumberToPack >= 0x0000000000000100)
1520 return 2;
1521 return 1;
1522 }
1523
1524 if (NumberToPack > 0)
1525 {
1526 // we have to make sure the number that gets encoded won't be interpreted as negative
1527 if (NumberToPack >= 0x0080000000000000)
1528 return 8;
1529 if (NumberToPack >= 0x0000800000000000)
1530 return 7;
1531 if (NumberToPack >= 0x0000008000000000)
1532 return 6;
1533 if (NumberToPack >= 0x0000000080000000)
1534 return 5;
1535 if (NumberToPack >= 0x0000000000800000)
1536 return 4;
1537 if (NumberToPack >= 0x0000000000008000)
1538 return 3;
1539 if (NumberToPack >= 0x0000000000000080)
1540 return 2;
1541 }
1542 else
1543 {
1544 // negative number
1545 if (NumberToPack <= 0xff80000000000000)
1546 return 8;
1547 if (NumberToPack <= 0xffff800000000000)
1548 return 7;
1549 if (NumberToPack <= 0xffffff8000000000)
1550 return 6;
1551 if (NumberToPack <= 0xffffffff80000000)
1552 return 5;
1553 if (NumberToPack <= 0xffffffffff800000)
1554 return 4;
1555 if (NumberToPack <= 0xffffffffffff8000)
1556 return 3;
1557 if (NumberToPack <= 0xffffffffffffff80)
1558 return 2;
1559 }
1560 return 1;
1561 }
1562
1563 NTSTATUS
1564 GetLastClusterInDataRun(PDEVICE_EXTENSION Vcb, PNTFS_ATTR_RECORD Attribute, PULONGLONG LastCluster)
1565 {
1566 LONGLONG DataRunOffset;
1567 ULONGLONG DataRunLength;
1568 LONGLONG DataRunStartLCN;
1569
1570 ULONGLONG LastLCN = 0;
1571 PUCHAR DataRun = (PUCHAR)Attribute + Attribute->NonResident.MappingPairsOffset;
1572
1573 if (!Attribute->IsNonResident)
1574 return STATUS_INVALID_PARAMETER;
1575
1576 while (1)
1577 {
1578 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1579
1580 if (DataRunOffset != -1)
1581 {
1582 // Normal data run.
1583 DataRunStartLCN = LastLCN + DataRunOffset;
1584 LastLCN = DataRunStartLCN;
1585 *LastCluster = LastLCN + DataRunLength - 1;
1586 }
1587
1588 if (*DataRun == 0)
1589 break;
1590 }
1591
1592 return STATUS_SUCCESS;
1593 }
1594
1595 PSTANDARD_INFORMATION
1596 GetStandardInformationFromRecord(PDEVICE_EXTENSION Vcb,
1597 PFILE_RECORD_HEADER FileRecord)
1598 {
1599 NTSTATUS Status;
1600 FIND_ATTR_CONTXT Context;
1601 PNTFS_ATTR_RECORD Attribute;
1602 PSTANDARD_INFORMATION StdInfo;
1603
1604 Status = FindFirstAttribute(&Context, Vcb, FileRecord, FALSE, &Attribute);
1605 while (NT_SUCCESS(Status))
1606 {
1607 if (Attribute->Type == AttributeStandardInformation)
1608 {
1609 StdInfo = (PSTANDARD_INFORMATION)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
1610 FindCloseAttribute(&Context);
1611 return StdInfo;
1612 }
1613
1614 Status = FindNextAttribute(&Context, &Attribute);
1615 }
1616
1617 FindCloseAttribute(&Context);
1618 return NULL;
1619 }
1620
1621 /**
1622 * @name GetFileNameAttributeLength
1623 * @implemented
1624 *
1625 * Returns the size of a given FILENAME_ATTRIBUTE, in bytes.
1626 *
1627 * @param FileNameAttribute
1628 * Pointer to a FILENAME_ATTRIBUTE to determine the size of.
1629 *
1630 * @remarks
1631 * The length of a FILENAME_ATTRIBUTE is variable and is dependent on the length of the file name stored at the end.
1632 * This function operates on the FILENAME_ATTRIBUTE proper, so don't try to pass it a PNTFS_ATTR_RECORD.
1633 */
1634 ULONG GetFileNameAttributeLength(PFILENAME_ATTRIBUTE FileNameAttribute)
1635 {
1636 ULONG Length = FIELD_OFFSET(FILENAME_ATTRIBUTE, Name) + (FileNameAttribute->NameLength * sizeof(WCHAR));
1637 return Length;
1638 }
1639
1640 PFILENAME_ATTRIBUTE
1641 GetBestFileNameFromRecord(PDEVICE_EXTENSION Vcb,
1642 PFILE_RECORD_HEADER FileRecord)
1643 {
1644 PFILENAME_ATTRIBUTE FileName;
1645
1646 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_POSIX);
1647 if (FileName == NULL)
1648 {
1649 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_WIN32);
1650 if (FileName == NULL)
1651 {
1652 FileName = GetFileNameFromRecord(Vcb, FileRecord, NTFS_FILE_NAME_DOS);
1653 }
1654 }
1655
1656 return FileName;
1657 }
1658
1659 /* EOF */