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