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