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