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