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