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