f03074cdfda337a52232d68c2b6e978f7bbdacb4
[reactos.git] / drivers / filesystems / ntfs / mft.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 2002, 2014 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/mft.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Eric Kohl
24 * Valentin Verkhovsky
25 * Pierre Schweitzer (pierre@reactos.org)
26 * Hervé Poussineau (hpoussin@reactos.org)
27 * Trevor Thompson
28 */
29
30 /* INCLUDES *****************************************************************/
31
32 #include "ntfs.h"
33 #include <ntintsafe.h>
34
35 #define NDEBUG
36 #include <debug.h>
37
38 /* FUNCTIONS ****************************************************************/
39
40 PNTFS_ATTR_CONTEXT
41 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
42 {
43 PNTFS_ATTR_CONTEXT Context;
44
45 Context = ExAllocateFromNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList);
46 if(!Context)
47 {
48 DPRINT1("Error: Unable to allocate memory for context!\n");
49 return NULL;
50 }
51
52 // Allocate memory for a copy of the attribute
53 Context->pRecord = ExAllocatePoolWithTag(NonPagedPool, AttrRecord->Length, TAG_NTFS);
54 if(!Context->pRecord)
55 {
56 DPRINT1("Error: Unable to allocate memory for attribute record!\n");
57 ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context);
58 return NULL;
59 }
60
61 // Copy the attribute
62 RtlCopyMemory(Context->pRecord, AttrRecord, AttrRecord->Length);
63
64 if (AttrRecord->IsNonResident)
65 {
66 LONGLONG DataRunOffset;
67 ULONGLONG DataRunLength;
68 ULONGLONG NextVBN = 0;
69 PUCHAR DataRun = (PUCHAR)((ULONG_PTR)Context->pRecord + Context->pRecord->NonResident.MappingPairsOffset);
70
71 Context->CacheRun = DataRun;
72 Context->CacheRunOffset = 0;
73 Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
74 Context->CacheRunLength = DataRunLength;
75 if (DataRunOffset != -1)
76 {
77 /* Normal run. */
78 Context->CacheRunStartLCN =
79 Context->CacheRunLastLCN = DataRunOffset;
80 }
81 else
82 {
83 /* Sparse run. */
84 Context->CacheRunStartLCN = -1;
85 Context->CacheRunLastLCN = 0;
86 }
87 Context->CacheRunCurrentOffset = 0;
88
89 // Convert the data runs to a map control block
90 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN)))
91 {
92 DPRINT1("Unable to convert data runs to MCB!\n");
93 ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
94 ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context);
95 return NULL;
96 }
97 }
98
99 return Context;
100 }
101
102
103 VOID
104 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
105 {
106 if (Context->pRecord)
107 {
108 if (Context->pRecord->IsNonResident)
109 {
110 FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
111 }
112
113 ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
114 }
115
116 ExFreeToNPagedLookasideList(&NtfsGlobalData->AttrCtxtLookasideList, Context);
117 }
118
119
120 /**
121 * @name FindAttribute
122 * @implemented
123 *
124 * Searches a file record for an attribute matching the given type and name.
125 *
126 * @param Offset
127 * Optional pointer to a ULONG that will receive the offset of the found attribute
128 * from the beginning of the record. Can be set to NULL.
129 */
130 NTSTATUS
131 FindAttribute(PDEVICE_EXTENSION Vcb,
132 PFILE_RECORD_HEADER MftRecord,
133 ULONG Type,
134 PCWSTR Name,
135 ULONG NameLength,
136 PNTFS_ATTR_CONTEXT * AttrCtx,
137 PULONG Offset)
138 {
139 BOOLEAN Found;
140 NTSTATUS Status;
141 FIND_ATTR_CONTXT Context;
142 PNTFS_ATTR_RECORD Attribute;
143
144 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %lu, %p, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx, Offset);
145
146 Found = FALSE;
147 Status = FindFirstAttribute(&Context, Vcb, MftRecord, FALSE, &Attribute);
148 while (NT_SUCCESS(Status))
149 {
150 if (Attribute->Type == Type && Attribute->NameLength == NameLength)
151 {
152 if (NameLength != 0)
153 {
154 PWCHAR AttrName;
155
156 AttrName = (PWCHAR)((PCHAR)Attribute + Attribute->NameOffset);
157 DPRINT("%.*S, %.*S\n", Attribute->NameLength, AttrName, NameLength, Name);
158 if (RtlCompareMemory(AttrName, Name, NameLength * sizeof(WCHAR)) == (NameLength * sizeof(WCHAR)))
159 {
160 Found = TRUE;
161 }
162 }
163 else
164 {
165 Found = TRUE;
166 }
167
168 if (Found)
169 {
170 /* Found it, fill up the context and return. */
171 DPRINT("Found context\n");
172 *AttrCtx = PrepareAttributeContext(Attribute);
173
174 (*AttrCtx)->FileMFTIndex = MftRecord->MFTRecordNumber;
175
176 if (Offset != NULL)
177 *Offset = Context.Offset;
178
179 FindCloseAttribute(&Context);
180 return STATUS_SUCCESS;
181 }
182 }
183
184 Status = FindNextAttribute(&Context, &Attribute);
185 }
186
187 FindCloseAttribute(&Context);
188 return STATUS_OBJECT_NAME_NOT_FOUND;
189 }
190
191
192 ULONGLONG
193 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord)
194 {
195 if (AttrRecord->IsNonResident)
196 return AttrRecord->NonResident.AllocatedSize;
197 else
198 return ALIGN_UP_BY(AttrRecord->Resident.ValueLength, ATTR_RECORD_ALIGNMENT);
199 }
200
201
202 ULONGLONG
203 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
204 {
205 if (AttrRecord->IsNonResident)
206 return AttrRecord->NonResident.DataSize;
207 else
208 return AttrRecord->Resident.ValueLength;
209 }
210
211 /**
212 * @name IncreaseMftSize
213 * @implemented
214 *
215 * Increases the size of the master file table on a volume, increasing the space available for file records.
216 *
217 * @param Vcb
218 * Pointer to the VCB (DEVICE_EXTENSION) of the target volume.
219 *
220 *
221 * @param CanWait
222 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
223 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
224 *
225 * @return
226 * STATUS_SUCCESS on success.
227 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
228 * STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap.
229 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
230 *
231 * @remarks
232 * Increases the size of the Master File Table by 64 records. Bitmap entries for the new records are cleared,
233 * and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO.
234 * This function will wait for exlusive access to the volume fcb.
235 */
236 NTSTATUS
237 IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
238 {
239 PNTFS_ATTR_CONTEXT BitmapContext;
240 LARGE_INTEGER BitmapSize;
241 LARGE_INTEGER DataSize;
242 LONGLONG BitmapSizeDifference;
243 ULONG NewRecords = ATTR_RECORD_ALIGNMENT * 8; // Allocate one new record for every bit of every byte we'll be adding to the bitmap
244 ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * NewRecords;
245 ULONG BitmapOffset;
246 PUCHAR BitmapBuffer;
247 ULONGLONG BitmapBytes;
248 ULONGLONG NewBitmapSize;
249 ULONGLONG FirstNewMftIndex;
250 ULONG BytesRead;
251 ULONG LengthWritten;
252 PFILE_RECORD_HEADER BlankFileRecord;
253 ULONG i;
254 NTSTATUS Status;
255
256 DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb, CanWait ? "TRUE" : "FALSE");
257
258 // We need exclusive access to the mft while we change its size
259 if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), CanWait))
260 {
261 return STATUS_CANT_WAIT;
262 }
263
264 // Create a blank file record that will be used later
265 BlankFileRecord = NtfsCreateEmptyFileRecord(Vcb);
266 if (!BlankFileRecord)
267 {
268 DPRINT1("Error: Unable to create empty file record!\n");
269 return STATUS_INSUFFICIENT_RESOURCES;
270 }
271
272 // Clear the flags (file record is not in use)
273 BlankFileRecord->Flags = 0;
274
275 // Find the bitmap attribute of master file table
276 Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
277 if (!NT_SUCCESS(Status))
278 {
279 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
280 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
281 ExReleaseResourceLite(&(Vcb->DirResource));
282 return Status;
283 }
284
285 // Get size of Bitmap Attribute
286 BitmapSize.QuadPart = AttributeDataLength(BitmapContext->pRecord);
287
288 // Calculate the new mft size
289 DataSize.QuadPart = AttributeDataLength(Vcb->MFTContext->pRecord) + DataSizeDifference;
290
291 // Find the index of the first Mft entry that will be created
292 FirstNewMftIndex = AttributeDataLength(Vcb->MFTContext->pRecord) / Vcb->NtfsInfo.BytesPerFileRecord;
293
294 // Determine how many bytes will make up the bitmap
295 BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8;
296 if ((DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord) % 8 != 0)
297 BitmapBytes++;
298
299 // Windows will always keep the number of bytes in a bitmap as a multiple of 8, so no bytes are wasted on slack
300 BitmapBytes = ALIGN_UP_BY(BitmapBytes, ATTR_RECORD_ALIGNMENT);
301
302 // Determine how much we need to adjust the bitmap size (it's possible we don't)
303 BitmapSizeDifference = BitmapBytes - BitmapSize.QuadPart;
304 NewBitmapSize = max(BitmapSize.QuadPart + BitmapSizeDifference, BitmapSize.QuadPart);
305
306 // Allocate memory for the bitmap
307 BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, NewBitmapSize, TAG_NTFS);
308 if (!BitmapBuffer)
309 {
310 DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
311 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
312 ExReleaseResourceLite(&(Vcb->DirResource));
313 ReleaseAttributeContext(BitmapContext);
314 return STATUS_INSUFFICIENT_RESOURCES;
315 }
316
317 // Zero the bytes we'll be adding
318 RtlZeroMemory(BitmapBuffer, NewBitmapSize);
319
320 // Read the bitmap attribute
321 BytesRead = ReadAttribute(Vcb,
322 BitmapContext,
323 0,
324 (PCHAR)BitmapBuffer,
325 BitmapSize.LowPart);
326 if (BytesRead != BitmapSize.LowPart)
327 {
328 DPRINT1("ERROR: Bytes read != Bitmap size!\n");
329 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
330 ExReleaseResourceLite(&(Vcb->DirResource));
331 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
332 ReleaseAttributeContext(BitmapContext);
333 return STATUS_INVALID_PARAMETER;
334 }
335
336 // Increase the mft size
337 Status = SetNonResidentAttributeDataLength(Vcb, Vcb->MFTContext, Vcb->MftDataOffset, Vcb->MasterFileTable, &DataSize);
338 if (!NT_SUCCESS(Status))
339 {
340 DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
341 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
342 ExReleaseResourceLite(&(Vcb->DirResource));
343 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
344 ReleaseAttributeContext(BitmapContext);
345 return Status;
346 }
347
348 // We'll need to find the bitmap again, because its offset will have changed after resizing the data attribute
349 ReleaseAttributeContext(BitmapContext);
350 Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset);
351 if (!NT_SUCCESS(Status))
352 {
353 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
354 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
355 ExReleaseResourceLite(&(Vcb->DirResource));
356 return Status;
357 }
358
359 // If the bitmap grew
360 if (BitmapSizeDifference > 0)
361 {
362 // Set the new bitmap size
363 BitmapSize.QuadPart = NewBitmapSize;
364 if (BitmapContext->pRecord->IsNonResident)
365 Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
366 else
367 Status = SetResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
368
369 if (!NT_SUCCESS(Status))
370 {
371 DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
372 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
373 ExReleaseResourceLite(&(Vcb->DirResource));
374 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
375 ReleaseAttributeContext(BitmapContext);
376 return Status;
377 }
378 }
379
380 NtfsDumpFileAttributes(Vcb, Vcb->MasterFileTable);
381
382 // Update the file record with the new attribute sizes
383 Status = UpdateFileRecord(Vcb, Vcb->VolumeFcb->MFTIndex, Vcb->MasterFileTable);
384 if (!NT_SUCCESS(Status))
385 {
386 DPRINT1("ERROR: Failed to update $MFT file record!\n");
387 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
388 ExReleaseResourceLite(&(Vcb->DirResource));
389 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
390 ReleaseAttributeContext(BitmapContext);
391 return Status;
392 }
393
394 // Write out the new bitmap
395 Status = WriteAttribute(Vcb, BitmapContext, 0, BitmapBuffer, NewBitmapSize, &LengthWritten, Vcb->MasterFileTable);
396 if (!NT_SUCCESS(Status))
397 {
398 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
399 ExReleaseResourceLite(&(Vcb->DirResource));
400 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
401 ReleaseAttributeContext(BitmapContext);
402 DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n");
403 return Status;
404 }
405
406 // Create blank records for the new file record entries.
407 for (i = 0; i < NewRecords; i++)
408 {
409 Status = UpdateFileRecord(Vcb, FirstNewMftIndex + i, BlankFileRecord);
410 if (!NT_SUCCESS(Status))
411 {
412 DPRINT1("ERROR: Failed to write blank file record!\n");
413 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
414 ExReleaseResourceLite(&(Vcb->DirResource));
415 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
416 ReleaseAttributeContext(BitmapContext);
417 return Status;
418 }
419 }
420
421 // Update the mft mirror
422 Status = UpdateMftMirror(Vcb);
423
424 // Cleanup
425 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, BlankFileRecord);
426 ExReleaseResourceLite(&(Vcb->DirResource));
427 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
428 ReleaseAttributeContext(BitmapContext);
429
430 return Status;
431 }
432
433 /**
434 * @name MoveAttributes
435 * @implemented
436 *
437 * Moves a block of attributes to a new location in the file Record. The attribute at FirstAttributeToMove
438 * and every attribute after that will be moved to MoveTo.
439 *
440 * @param DeviceExt
441 * Pointer to the DEVICE_EXTENSION (VCB) of the target volume.
442 *
443 * @param FirstAttributeToMove
444 * Pointer to the first NTFS_ATTR_RECORD that needs to be moved. This pointer must reside within a file record.
445 *
446 * @param FirstAttributeOffset
447 * Offset of FirstAttributeToMove relative to the beginning of the file record.
448 *
449 * @param MoveTo
450 * ULONG_PTR with the memory location that will be the new location of the first attribute being moved.
451 *
452 * @return
453 * The new location of the final attribute (i.e. AttributeEnd marker).
454 */
455 PNTFS_ATTR_RECORD
456 MoveAttributes(PDEVICE_EXTENSION DeviceExt,
457 PNTFS_ATTR_RECORD FirstAttributeToMove,
458 ULONG FirstAttributeOffset,
459 ULONG_PTR MoveTo)
460 {
461 // Get the size of all attributes after this one
462 ULONG MemBlockSize = 0;
463 PNTFS_ATTR_RECORD CurrentAttribute = FirstAttributeToMove;
464 ULONG CurrentOffset = FirstAttributeOffset;
465 PNTFS_ATTR_RECORD FinalAttribute;
466
467 while (CurrentAttribute->Type != AttributeEnd && CurrentOffset < DeviceExt->NtfsInfo.BytesPerFileRecord)
468 {
469 CurrentOffset += CurrentAttribute->Length;
470 MemBlockSize += CurrentAttribute->Length;
471 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
472 }
473
474 FinalAttribute = (PNTFS_ATTR_RECORD)(MoveTo + MemBlockSize);
475 MemBlockSize += sizeof(ULONG) * 2; // Add the AttributeEnd and file record end
476
477 ASSERT(MemBlockSize % ATTR_RECORD_ALIGNMENT == 0);
478
479 // Move the attributes after this one
480 RtlMoveMemory((PCHAR)MoveTo, FirstAttributeToMove, MemBlockSize);
481
482 return FinalAttribute;
483 }
484
485 NTSTATUS
486 InternalSetResidentAttributeLength(PDEVICE_EXTENSION DeviceExt,
487 PNTFS_ATTR_CONTEXT AttrContext,
488 PFILE_RECORD_HEADER FileRecord,
489 ULONG AttrOffset,
490 ULONG DataSize)
491 {
492 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
493 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
494 PNTFS_ATTR_RECORD FinalAttribute;
495 ULONG OldAttributeLength = Destination->Length;
496 ULONG NextAttributeOffset;
497
498 DPRINT1("InternalSetResidentAttributeLength( %p, %p, %p, %lu, %lu )\n", DeviceExt, AttrContext, FileRecord, AttrOffset, DataSize);
499
500 ASSERT(!AttrContext->pRecord->IsNonResident);
501
502 // Update ValueLength Field
503 Destination->Resident.ValueLength = DataSize;
504
505 // Calculate the record length and end marker offset
506 Destination->Length = ALIGN_UP_BY(DataSize + AttrContext->pRecord->Resident.ValueOffset, ATTR_RECORD_ALIGNMENT);
507 NextAttributeOffset = AttrOffset + Destination->Length;
508
509 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
510 ASSERT(NextAttributeOffset % ATTR_RECORD_ALIGNMENT == 0);
511
512 // Will the new attribute be larger than the old one?
513 if (Destination->Length > OldAttributeLength)
514 {
515 // Free the old copy of the attribute in the context, as it will be the wrong length
516 ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
517
518 // Create a new copy of the attribute record for the context
519 AttrContext->pRecord = ExAllocatePoolWithTag(NonPagedPool, Destination->Length, TAG_NTFS);
520 if (!AttrContext->pRecord)
521 {
522 DPRINT1("Unable to allocate memory for attribute!\n");
523 return STATUS_INSUFFICIENT_RESOURCES;
524 }
525 RtlZeroMemory((PVOID)((ULONG_PTR)AttrContext->pRecord + OldAttributeLength), Destination->Length - OldAttributeLength);
526 RtlCopyMemory(AttrContext->pRecord, Destination, OldAttributeLength);
527 }
528
529 // Are there attributes after this one that need to be moved?
530 if (NextAttribute->Type != AttributeEnd)
531 {
532 // Move the attributes after this one
533 FinalAttribute = MoveAttributes(DeviceExt, NextAttribute, NextAttributeOffset, (ULONG_PTR)Destination + Destination->Length);
534 }
535 else
536 {
537 // advance to the final "attribute," adjust for the changed length of the attribute we're resizing
538 FinalAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)NextAttribute - OldAttributeLength + Destination->Length);
539 }
540
541 // Update pRecord's length
542 AttrContext->pRecord->Length = Destination->Length;
543 AttrContext->pRecord->Resident.ValueLength = DataSize;
544
545 // set the file record end
546 SetFileRecordEnd(FileRecord, FinalAttribute, FILE_RECORD_END);
547
548 //NtfsDumpFileRecord(DeviceExt, FileRecord);
549
550 return STATUS_SUCCESS;
551 }
552
553 /**
554 * @parameter FileRecord
555 * Pointer to a file record. Must be a full record at least
556 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
557 */
558 NTSTATUS
559 SetAttributeDataLength(PFILE_OBJECT FileObject,
560 PNTFS_FCB Fcb,
561 PNTFS_ATTR_CONTEXT AttrContext,
562 ULONG AttrOffset,
563 PFILE_RECORD_HEADER FileRecord,
564 PLARGE_INTEGER DataSize)
565 {
566 NTSTATUS Status = STATUS_SUCCESS;
567
568 DPRINT1("SetAttributeDataLength(%p, %p, %p, %lu, %p, %I64u)\n",
569 FileObject,
570 Fcb,
571 AttrContext,
572 AttrOffset,
573 FileRecord,
574 DataSize->QuadPart);
575
576 // are we truncating the file?
577 if (DataSize->QuadPart < AttributeDataLength(AttrContext->pRecord))
578 {
579 if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize))
580 {
581 DPRINT1("Can't truncate a memory-mapped file!\n");
582 return STATUS_USER_MAPPED_FILE;
583 }
584 }
585
586 if (AttrContext->pRecord->IsNonResident)
587 {
588 Status = SetNonResidentAttributeDataLength(Fcb->Vcb,
589 AttrContext,
590 AttrOffset,
591 FileRecord,
592 DataSize);
593 }
594 else
595 {
596 // resident attribute
597 Status = SetResidentAttributeDataLength(Fcb->Vcb,
598 AttrContext,
599 AttrOffset,
600 FileRecord,
601 DataSize);
602 }
603
604 if (!NT_SUCCESS(Status))
605 {
606 DPRINT1("ERROR: Failed to set size of attribute!\n");
607 return Status;
608 }
609
610 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
611
612 // write the updated file record back to disk
613 Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord);
614
615 if (NT_SUCCESS(Status))
616 {
617 if (AttrContext->pRecord->IsNonResident)
618 Fcb->RFCB.AllocationSize.QuadPart = AttrContext->pRecord->NonResident.AllocatedSize;
619 else
620 Fcb->RFCB.AllocationSize = *DataSize;
621 Fcb->RFCB.FileSize = *DataSize;
622 Fcb->RFCB.ValidDataLength = *DataSize;
623 CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
624 }
625
626 return STATUS_SUCCESS;
627 }
628
629 /**
630 * @name SetFileRecordEnd
631 * @implemented
632 *
633 * This small function sets a new endpoint for the file record. It set's the final
634 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
635 *
636 * @param FileRecord
637 * Pointer to the file record whose endpoint (length) will be set.
638 *
639 * @param AttrEnd
640 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
641 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
642 *
643 * @param EndMarker
644 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
645 * a file record, it preserves the final ULONG that previously ended the record, even though this
646 * value is (to my knowledge) never used. We emulate this behavior.
647 *
648 */
649 VOID
650 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
651 PNTFS_ATTR_RECORD AttrEnd,
652 ULONG EndMarker)
653 {
654 // Ensure AttrEnd is aligned on an 8-byte boundary, relative to FileRecord
655 ASSERT(((ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord) % ATTR_RECORD_ALIGNMENT == 0);
656
657 // mark the end of attributes
658 AttrEnd->Type = AttributeEnd;
659
660 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
661 AttrEnd->Length = EndMarker;
662
663 // recalculate bytes in use
664 FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2;
665 }
666
667 /**
668 * @name SetNonResidentAttributeDataLength
669 * @implemented
670 *
671 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
672 *
673 * @param Vcb
674 * Pointer to a DEVICE_EXTENSION describing the target disk.
675 *
676 * @param AttrContext
677 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
678 *
679 * @param AttrOffset
680 * Offset, from the beginning of the record, of the attribute being sized.
681 *
682 * @param FileRecord
683 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
684 * not just the header.
685 *
686 * @param DataSize
687 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
688 *
689 * @return
690 * STATUS_SUCCESS on success;
691 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
692 * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
693 *
694 * @remarks
695 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
696 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
697 * any associated files. Synchronization is the callers responsibility.
698 */
699 NTSTATUS
700 SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
701 PNTFS_ATTR_CONTEXT AttrContext,
702 ULONG AttrOffset,
703 PFILE_RECORD_HEADER FileRecord,
704 PLARGE_INTEGER DataSize)
705 {
706 NTSTATUS Status = STATUS_SUCCESS;
707 ULONG BytesPerCluster = Vcb->NtfsInfo.BytesPerCluster;
708 ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster);
709 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
710 ULONG ExistingClusters = AttrContext->pRecord->NonResident.AllocatedSize / BytesPerCluster;
711
712 ASSERT(AttrContext->pRecord->IsNonResident);
713
714 // do we need to increase the allocation size?
715 if (AttrContext->pRecord->NonResident.AllocatedSize < AllocationSize)
716 {
717 ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters;
718 LARGE_INTEGER LastClusterInDataRun;
719 ULONG NextAssignedCluster;
720 ULONG AssignedClusters;
721
722 if (ExistingClusters == 0)
723 {
724 LastClusterInDataRun.QuadPart = 0;
725 }
726 else
727 {
728 if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
729 (LONGLONG)AttrContext->pRecord->NonResident.HighestVCN,
730 (PLONGLONG)&LastClusterInDataRun.QuadPart,
731 NULL,
732 NULL,
733 NULL,
734 NULL))
735 {
736 DPRINT1("Error looking up final large MCB entry!\n");
737
738 // Most likely, HighestVCN went above the largest mapping
739 DPRINT1("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN);
740 return STATUS_INVALID_PARAMETER;
741 }
742 }
743
744 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
745 DPRINT("Highest VCN of record: %I64u\n", AttrContext->pRecord->NonResident.HighestVCN);
746
747 while (ClustersNeeded > 0)
748 {
749 Status = NtfsAllocateClusters(Vcb,
750 LastClusterInDataRun.LowPart + 1,
751 ClustersNeeded,
752 &NextAssignedCluster,
753 &AssignedClusters);
754
755 if (!NT_SUCCESS(Status))
756 {
757 DPRINT1("Error: Unable to allocate requested clusters!\n");
758 return Status;
759 }
760
761 // now we need to add the clusters we allocated to the data run
762 Status = AddRun(Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters);
763 if (!NT_SUCCESS(Status))
764 {
765 DPRINT1("Error: Unable to add data run!\n");
766 return Status;
767 }
768
769 ClustersNeeded -= AssignedClusters;
770 LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1;
771 }
772 }
773 else if (AttrContext->pRecord->NonResident.AllocatedSize > AllocationSize)
774 {
775 // shrink allocation size
776 ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster);
777 Status = FreeClusters(Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree);
778 }
779
780 // TODO: is the file compressed, encrypted, or sparse?
781
782 AttrContext->pRecord->NonResident.AllocatedSize = AllocationSize;
783 AttrContext->pRecord->NonResident.DataSize = DataSize->QuadPart;
784 AttrContext->pRecord->NonResident.InitializedSize = DataSize->QuadPart;
785
786 DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
787 DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
788 DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
789
790 // HighestVCN seems to be set incorrectly somewhere. Apply a hack-fix to reset it.
791 // HACKHACK FIXME: Fix for sparse files; this math won't work in that case.
792 AttrContext->pRecord->NonResident.HighestVCN = ((ULONGLONG)AllocationSize / Vcb->NtfsInfo.BytesPerCluster) - 1;
793 DestinationAttribute->NonResident.HighestVCN = AttrContext->pRecord->NonResident.HighestVCN;
794
795 DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
796
797 return Status;
798 }
799
800 /**
801 * @name SetResidentAttributeDataLength
802 * @implemented
803 *
804 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
805 *
806 * @param Vcb
807 * Pointer to a DEVICE_EXTENSION describing the target disk.
808 *
809 * @param AttrContext
810 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
811 *
812 * @param AttrOffset
813 * Offset, from the beginning of the record, of the attribute being sized.
814 *
815 * @param FileRecord
816 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
817 * not just the header.
818 *
819 * @param DataSize
820 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
821 *
822 * @return
823 * STATUS_SUCCESS on success;
824 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
825 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
826 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
827 * last attribute listed in the file record.
828 *
829 * @remarks
830 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
831 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
832 * any associated files. Synchronization is the callers responsibility.
833 */
834 NTSTATUS
835 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
836 PNTFS_ATTR_CONTEXT AttrContext,
837 ULONG AttrOffset,
838 PFILE_RECORD_HEADER FileRecord,
839 PLARGE_INTEGER DataSize)
840 {
841 NTSTATUS Status;
842
843 // find the next attribute
844 ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
845 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
846
847 ASSERT(!AttrContext->pRecord->IsNonResident);
848
849 //NtfsDumpFileAttributes(Vcb, FileRecord);
850
851 // Do we need to increase the data length?
852 if (DataSize->QuadPart > AttrContext->pRecord->Resident.ValueLength)
853 {
854 // There's usually padding at the end of a record. Do we need to extend past it?
855 ULONG MaxValueLength = AttrContext->pRecord->Length - AttrContext->pRecord->Resident.ValueOffset;
856 if (MaxValueLength < DataSize->LowPart)
857 {
858 // If this is the last attribute, we could move the end marker to the very end of the file record
859 MaxValueLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
860
861 if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
862 {
863 // convert attribute to non-resident
864 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
865 PNTFS_ATTR_RECORD NewRecord;
866 LARGE_INTEGER AttribDataSize;
867 PVOID AttribData;
868 ULONG NewRecordLength;
869 ULONG EndAttributeOffset;
870 ULONG LengthWritten;
871
872 DPRINT1("Converting attribute to non-resident.\n");
873
874 AttribDataSize.QuadPart = AttrContext->pRecord->Resident.ValueLength;
875
876 // Is there existing data we need to back-up?
877 if (AttribDataSize.QuadPart > 0)
878 {
879 AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
880 if (AttribData == NULL)
881 {
882 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
883 return STATUS_INSUFFICIENT_RESOURCES;
884 }
885
886 // read data to temp buffer
887 Status = ReadAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
888 if (!NT_SUCCESS(Status))
889 {
890 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
891 ExFreePoolWithTag(AttribData, TAG_NTFS);
892 return Status;
893 }
894 }
895
896 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
897
898 // The size of a 0-length, non-resident attribute will be 0x41 + the size of the attribute name, aligned to an 8-byte boundary
899 NewRecordLength = ALIGN_UP_BY(0x41 + (AttrContext->pRecord->NameLength * sizeof(WCHAR)), ATTR_RECORD_ALIGNMENT);
900
901 // Create a new attribute record that will store the 0-length, non-resident attribute
902 NewRecord = ExAllocatePoolWithTag(NonPagedPool, NewRecordLength, TAG_NTFS);
903
904 // Zero out the NonResident structure
905 RtlZeroMemory(NewRecord, NewRecordLength);
906
907 // Copy the data that's common to both non-resident and resident attributes
908 RtlCopyMemory(NewRecord, AttrContext->pRecord, FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.ValueLength));
909
910 // if there's a name
911 if (AttrContext->pRecord->NameLength != 0)
912 {
913 // copy the name
914 // An attribute name will be located at offset 0x18 for a resident attribute, 0x40 for non-resident
915 RtlCopyMemory((PCHAR)((ULONG_PTR)NewRecord + 0x40),
916 (PCHAR)((ULONG_PTR)AttrContext->pRecord + 0x18),
917 AttrContext->pRecord->NameLength * sizeof(WCHAR));
918 }
919
920 // update the mapping pairs offset, which will be 0x40 (size of a non-resident header) + length in bytes of the name
921 NewRecord->NonResident.MappingPairsOffset = 0x40 + (AttrContext->pRecord->NameLength * sizeof(WCHAR));
922
923 // update the end of the file record
924 // calculate position of end markers (1 byte for empty data run)
925 EndAttributeOffset = AttrOffset + NewRecord->NonResident.MappingPairsOffset + 1;
926 EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, ATTR_RECORD_ALIGNMENT);
927
928 // Update the length
929 NewRecord->Length = EndAttributeOffset - AttrOffset;
930
931 ASSERT(NewRecord->Length == NewRecordLength);
932
933 // Copy the new attribute record into the file record
934 RtlCopyMemory(Destination, NewRecord, NewRecord->Length);
935
936 // Update the file record end
937 SetFileRecordEnd(FileRecord,
938 (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset),
939 FILE_RECORD_END);
940
941 // Initialize the MCB, potentially catch an exception
942 _SEH2_TRY
943 {
944 FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
945 }
946 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
947 {
948 DPRINT1("Unable to create LargeMcb!\n");
949 if (AttribDataSize.QuadPart > 0)
950 ExFreePoolWithTag(AttribData, TAG_NTFS);
951 ExFreePoolWithTag(NewRecord, TAG_NTFS);
952 _SEH2_YIELD(return _SEH2_GetExceptionCode());
953 } _SEH2_END;
954
955 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized)
956 NewRecord->IsNonResident = Destination->IsNonResident = 1;
957
958 // Update file record on disk
959 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
960 if (!NT_SUCCESS(Status))
961 {
962 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
963 if (AttribDataSize.QuadPart > 0)
964 ExFreePoolWithTag(AttribData, TAG_NTFS);
965 ExFreePoolWithTag(NewRecord, TAG_NTFS);
966 return Status;
967 }
968
969 // Now we need to free the old copy of the attribute record in the context and replace it with the new one
970 ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
971 AttrContext->pRecord = NewRecord;
972
973 // Now we can treat the attribute as non-resident and enlarge it normally
974 Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize);
975 if (!NT_SUCCESS(Status))
976 {
977 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
978 if (AttribDataSize.QuadPart > 0)
979 ExFreePoolWithTag(AttribData, TAG_NTFS);
980 return Status;
981 }
982
983 // restore the back-up attribute, if we made one
984 if (AttribDataSize.QuadPart > 0)
985 {
986 Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten, FileRecord);
987 if (!NT_SUCCESS(Status))
988 {
989 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
990 // TODO: Reverse migration so no data is lost
991 ExFreePoolWithTag(AttribData, TAG_NTFS);
992 return Status;
993 }
994
995 ExFreePoolWithTag(AttribData, TAG_NTFS);
996 }
997 }
998 }
999 }
1000
1001 // set the new length of the resident attribute (if we didn't migrate it)
1002 if (!AttrContext->pRecord->IsNonResident)
1003 return InternalSetResidentAttributeLength(Vcb, AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
1004
1005 return STATUS_SUCCESS;
1006 }
1007
1008 ULONG
1009 ReadAttribute(PDEVICE_EXTENSION Vcb,
1010 PNTFS_ATTR_CONTEXT Context,
1011 ULONGLONG Offset,
1012 PCHAR Buffer,
1013 ULONG Length)
1014 {
1015 ULONGLONG LastLCN;
1016 PUCHAR DataRun;
1017 LONGLONG DataRunOffset;
1018 ULONGLONG DataRunLength;
1019 LONGLONG DataRunStartLCN;
1020 ULONGLONG CurrentOffset;
1021 ULONG ReadLength;
1022 ULONG AlreadyRead;
1023 NTSTATUS Status;
1024
1025 //TEMPTEMP
1026 PUCHAR TempBuffer;
1027
1028 if (!Context->pRecord->IsNonResident)
1029 {
1030 // We need to truncate Offset to a ULONG for pointer arithmetic
1031 // The check below should ensure that Offset is well within the range of 32 bits
1032 ULONG LittleOffset = (ULONG)Offset;
1033
1034 // Ensure that offset isn't beyond the end of the attribute
1035 if (Offset > Context->pRecord->Resident.ValueLength)
1036 return 0;
1037 if (Offset + Length > Context->pRecord->Resident.ValueLength)
1038 Length = (ULONG)(Context->pRecord->Resident.ValueLength - Offset);
1039
1040 RtlCopyMemory(Buffer, (PVOID)((ULONG_PTR)Context->pRecord + Context->pRecord->Resident.ValueOffset + LittleOffset), Length);
1041 return Length;
1042 }
1043
1044 /*
1045 * Non-resident attribute
1046 */
1047
1048 /*
1049 * I. Find the corresponding start data run.
1050 */
1051
1052 AlreadyRead = 0;
1053
1054 // FIXME: Cache seems to be non-working. Disable it for now
1055 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1056 if (0)
1057 {
1058 DataRun = Context->CacheRun;
1059 LastLCN = Context->CacheRunLastLCN;
1060 DataRunStartLCN = Context->CacheRunStartLCN;
1061 DataRunLength = Context->CacheRunLength;
1062 CurrentOffset = Context->CacheRunCurrentOffset;
1063 }
1064 else
1065 {
1066 //TEMPTEMP
1067 ULONG UsedBufferSize;
1068 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1069 if (TempBuffer == NULL)
1070 {
1071 return STATUS_INSUFFICIENT_RESOURCES;
1072 }
1073
1074 LastLCN = 0;
1075 CurrentOffset = 0;
1076
1077 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1078 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
1079 TempBuffer,
1080 Vcb->NtfsInfo.BytesPerFileRecord,
1081 &UsedBufferSize);
1082
1083 DataRun = TempBuffer;
1084
1085 while (1)
1086 {
1087 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1088 if (DataRunOffset != -1)
1089 {
1090 /* Normal data run. */
1091 DataRunStartLCN = LastLCN + DataRunOffset;
1092 LastLCN = DataRunStartLCN;
1093 }
1094 else
1095 {
1096 /* Sparse data run. */
1097 DataRunStartLCN = -1;
1098 }
1099
1100 if (Offset >= CurrentOffset &&
1101 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
1102 {
1103 break;
1104 }
1105
1106 if (*DataRun == 0)
1107 {
1108 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1109 return AlreadyRead;
1110 }
1111
1112 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1113 }
1114 }
1115
1116 /*
1117 * II. Go through the run list and read the data
1118 */
1119
1120 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
1121 if (DataRunStartLCN == -1)
1122 {
1123 RtlZeroMemory(Buffer, ReadLength);
1124 Status = STATUS_SUCCESS;
1125 }
1126 else
1127 {
1128 Status = NtfsReadDisk(Vcb->StorageDevice,
1129 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
1130 ReadLength,
1131 Vcb->NtfsInfo.BytesPerSector,
1132 (PVOID)Buffer,
1133 FALSE);
1134 }
1135 if (NT_SUCCESS(Status))
1136 {
1137 Length -= ReadLength;
1138 Buffer += ReadLength;
1139 AlreadyRead += ReadLength;
1140
1141 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
1142 {
1143 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1144 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1145 if (DataRunOffset != (ULONGLONG)-1)
1146 {
1147 DataRunStartLCN = LastLCN + DataRunOffset;
1148 LastLCN = DataRunStartLCN;
1149 }
1150 else
1151 DataRunStartLCN = -1;
1152 }
1153
1154 while (Length > 0)
1155 {
1156 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
1157 if (DataRunStartLCN == -1)
1158 RtlZeroMemory(Buffer, ReadLength);
1159 else
1160 {
1161 Status = NtfsReadDisk(Vcb->StorageDevice,
1162 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
1163 ReadLength,
1164 Vcb->NtfsInfo.BytesPerSector,
1165 (PVOID)Buffer,
1166 FALSE);
1167 if (!NT_SUCCESS(Status))
1168 break;
1169 }
1170
1171 Length -= ReadLength;
1172 Buffer += ReadLength;
1173 AlreadyRead += ReadLength;
1174
1175 /* We finished this request, but there still data in this data run. */
1176 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1177 break;
1178
1179 /*
1180 * Go to next run in the list.
1181 */
1182
1183 if (*DataRun == 0)
1184 break;
1185 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1186 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1187 if (DataRunOffset != -1)
1188 {
1189 /* Normal data run. */
1190 DataRunStartLCN = LastLCN + DataRunOffset;
1191 LastLCN = DataRunStartLCN;
1192 }
1193 else
1194 {
1195 /* Sparse data run. */
1196 DataRunStartLCN = -1;
1197 }
1198 } /* while */
1199
1200 } /* if Disk */
1201
1202 // TEMPTEMP
1203 if (Context->pRecord->IsNonResident)
1204 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1205
1206 Context->CacheRun = DataRun;
1207 Context->CacheRunOffset = Offset + AlreadyRead;
1208 Context->CacheRunStartLCN = DataRunStartLCN;
1209 Context->CacheRunLength = DataRunLength;
1210 Context->CacheRunLastLCN = LastLCN;
1211 Context->CacheRunCurrentOffset = CurrentOffset;
1212
1213 return AlreadyRead;
1214 }
1215
1216
1217 /**
1218 * @name WriteAttribute
1219 * @implemented
1220 *
1221 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1222 * and it still needs more documentation / cleaning up.
1223 *
1224 * @param Vcb
1225 * Volume Control Block indicating which volume to write the attribute to
1226 *
1227 * @param Context
1228 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1229 *
1230 * @param Offset
1231 * Offset, in bytes, from the beginning of the attribute indicating where to start
1232 * writing data
1233 *
1234 * @param Buffer
1235 * The data that's being written to the device
1236 *
1237 * @param Length
1238 * How much data will be written, in bytes
1239 *
1240 * @param RealLengthWritten
1241 * Pointer to a ULONG which will receive how much data was written, in bytes
1242 *
1243 * @param FileRecord
1244 * Optional pointer to a FILE_RECORD_HEADER that contains a copy of the file record
1245 * being written to. Can be NULL, in which case the file record will be read from disk.
1246 * If not-null, WriteAttribute() will skip reading from disk, and FileRecord
1247 * will be updated with the newly-written attribute before the function returns.
1248 *
1249 * @return
1250 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1251 * writing to a sparse file.
1252 *
1253 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1254 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1255 *
1256 */
1257
1258 NTSTATUS
1259 WriteAttribute(PDEVICE_EXTENSION Vcb,
1260 PNTFS_ATTR_CONTEXT Context,
1261 ULONGLONG Offset,
1262 const PUCHAR Buffer,
1263 ULONG Length,
1264 PULONG RealLengthWritten,
1265 PFILE_RECORD_HEADER FileRecord)
1266 {
1267 ULONGLONG LastLCN;
1268 PUCHAR DataRun;
1269 LONGLONG DataRunOffset;
1270 ULONGLONG DataRunLength;
1271 LONGLONG DataRunStartLCN;
1272 ULONGLONG CurrentOffset;
1273 ULONG WriteLength;
1274 NTSTATUS Status;
1275 PUCHAR SourceBuffer = Buffer;
1276 LONGLONG StartingOffset;
1277 BOOLEAN FileRecordAllocated = FALSE;
1278
1279 //TEMPTEMP
1280 PUCHAR TempBuffer;
1281
1282
1283 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten, FileRecord);
1284
1285 *RealLengthWritten = 0;
1286
1287 // is this a resident attribute?
1288 if (!Context->pRecord->IsNonResident)
1289 {
1290 ULONG AttributeOffset;
1291 PNTFS_ATTR_CONTEXT FoundContext;
1292 PNTFS_ATTR_RECORD Destination;
1293
1294 // Ensure requested data is within the bounds of the attribute
1295 ASSERT(Offset + Length <= Context->pRecord->Resident.ValueLength);
1296
1297 if (Offset + Length > Context->pRecord->Resident.ValueLength)
1298 {
1299 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1300 return STATUS_INVALID_PARAMETER;
1301 }
1302
1303 // Do we need to read the file record?
1304 if (FileRecord == NULL)
1305 {
1306 FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
1307 if (!FileRecord)
1308 {
1309 DPRINT1("Error: Couldn't allocate file record!\n");
1310 return STATUS_NO_MEMORY;
1311 }
1312
1313 FileRecordAllocated = TRUE;
1314
1315 // read the file record
1316 ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1317 }
1318
1319 // find where to write the attribute data to
1320 Status = FindAttribute(Vcb, FileRecord,
1321 Context->pRecord->Type,
1322 (PCWSTR)((ULONG_PTR)Context->pRecord + Context->pRecord->NameOffset),
1323 Context->pRecord->NameLength,
1324 &FoundContext,
1325 &AttributeOffset);
1326
1327 if (!NT_SUCCESS(Status))
1328 {
1329 DPRINT1("ERROR: Couldn't find matching attribute!\n");
1330 if(FileRecordAllocated)
1331 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord);
1332 return Status;
1333 }
1334
1335 Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttributeOffset);
1336
1337 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->pRecord->Resident.ValueLength);
1338
1339 // Will we be writing past the end of the allocated file record?
1340 if (Offset + Length + AttributeOffset + Context->pRecord->Resident.ValueOffset > Vcb->NtfsInfo.BytesPerFileRecord)
1341 {
1342 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1343 ReleaseAttributeContext(FoundContext);
1344 if (FileRecordAllocated)
1345 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord);
1346 return STATUS_INVALID_PARAMETER;
1347 }
1348
1349 // copy the data being written into the file record. We cast Offset to ULONG, which is safe because it's range has been verified.
1350 RtlCopyMemory((PCHAR)((ULONG_PTR)Destination + Context->pRecord->Resident.ValueOffset + (ULONG)Offset), Buffer, Length);
1351
1352 Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1353
1354 // Update the context's copy of the resident attribute
1355 ASSERT(Context->pRecord->Length == Destination->Length);
1356 RtlCopyMemory((PVOID)Context->pRecord, Destination, Context->pRecord->Length);
1357
1358 ReleaseAttributeContext(FoundContext);
1359 if (FileRecordAllocated)
1360 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, FileRecord);
1361
1362 if (NT_SUCCESS(Status))
1363 *RealLengthWritten = Length;
1364
1365 return Status;
1366 }
1367
1368 // This is a non-resident attribute.
1369
1370 // I. Find the corresponding start data run.
1371
1372 // FIXME: Cache seems to be non-working. Disable it for now
1373 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1374 /*if (0)
1375 {
1376 DataRun = Context->CacheRun;
1377 LastLCN = Context->CacheRunLastLCN;
1378 DataRunStartLCN = Context->CacheRunStartLCN;
1379 DataRunLength = Context->CacheRunLength;
1380 CurrentOffset = Context->CacheRunCurrentOffset;
1381 }
1382 else*/
1383 {
1384 ULONG UsedBufferSize;
1385 LastLCN = 0;
1386 CurrentOffset = 0;
1387
1388 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1389 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1390 if (TempBuffer == NULL)
1391 {
1392 return STATUS_INSUFFICIENT_RESOURCES;
1393 }
1394
1395 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
1396 TempBuffer,
1397 Vcb->NtfsInfo.BytesPerFileRecord,
1398 &UsedBufferSize);
1399
1400 DataRun = TempBuffer;
1401
1402 while (1)
1403 {
1404 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1405 if (DataRunOffset != -1)
1406 {
1407 // Normal data run.
1408 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1409 DataRunStartLCN = LastLCN + DataRunOffset;
1410 LastLCN = DataRunStartLCN;
1411 }
1412 else
1413 {
1414 // Sparse data run. We can't support writing to sparse files yet
1415 // (it may require increasing the allocation size).
1416 DataRunStartLCN = -1;
1417 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1418 Status = STATUS_NOT_IMPLEMENTED;
1419 goto Cleanup;
1420 }
1421
1422 // Have we reached the data run we're trying to write to?
1423 if (Offset >= CurrentOffset &&
1424 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
1425 {
1426 break;
1427 }
1428
1429 if (*DataRun == 0)
1430 {
1431 // We reached the last assigned cluster
1432 // TODO: assign new clusters to the end of the file.
1433 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1434 // [We can reach here by creating a new file record when the MFT isn't large enough]
1435 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1436 Status = STATUS_END_OF_FILE;
1437 goto Cleanup;
1438 }
1439
1440 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1441 }
1442 }
1443
1444 // II. Go through the run list and write the data
1445
1446 /* REVIEWME -- As adapted from NtfsReadAttribute():
1447 We seem to be making a special case for the first applicable data run, but I'm not sure why.
1448 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1449
1450 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
1451
1452 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
1453
1454 // Write the data to the disk
1455 Status = NtfsWriteDisk(Vcb->StorageDevice,
1456 StartingOffset,
1457 WriteLength,
1458 Vcb->NtfsInfo.BytesPerSector,
1459 (PVOID)SourceBuffer);
1460
1461 // Did the write fail?
1462 if (!NT_SUCCESS(Status))
1463 {
1464 Context->CacheRun = DataRun;
1465 Context->CacheRunOffset = Offset;
1466 Context->CacheRunStartLCN = DataRunStartLCN;
1467 Context->CacheRunLength = DataRunLength;
1468 Context->CacheRunLastLCN = LastLCN;
1469 Context->CacheRunCurrentOffset = CurrentOffset;
1470
1471 goto Cleanup;
1472 }
1473
1474 Length -= WriteLength;
1475 SourceBuffer += WriteLength;
1476 *RealLengthWritten += WriteLength;
1477
1478 // Did we write to the end of the data run?
1479 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
1480 {
1481 // Advance to the next data run
1482 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1483 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1484
1485 if (DataRunOffset != (ULONGLONG)-1)
1486 {
1487 DataRunStartLCN = LastLCN + DataRunOffset;
1488 LastLCN = DataRunStartLCN;
1489 }
1490 else
1491 DataRunStartLCN = -1;
1492 }
1493
1494 // Do we have more data to write?
1495 while (Length > 0)
1496 {
1497 // Make sure we don't write past the end of the current data run
1498 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
1499
1500 // Are we dealing with a sparse data run?
1501 if (DataRunStartLCN == -1)
1502 {
1503 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1504 Status = STATUS_NOT_IMPLEMENTED;
1505 goto Cleanup;
1506 }
1507 else
1508 {
1509 // write the data to the disk
1510 Status = NtfsWriteDisk(Vcb->StorageDevice,
1511 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
1512 WriteLength,
1513 Vcb->NtfsInfo.BytesPerSector,
1514 (PVOID)SourceBuffer);
1515 if (!NT_SUCCESS(Status))
1516 break;
1517 }
1518
1519 Length -= WriteLength;
1520 SourceBuffer += WriteLength;
1521 *RealLengthWritten += WriteLength;
1522
1523 // We finished this request, but there's still data in this data run.
1524 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1525 break;
1526
1527 // Go to next run in the list.
1528
1529 if (*DataRun == 0)
1530 {
1531 // that was the last run
1532 if (Length > 0)
1533 {
1534 // Failed sanity check.
1535 DPRINT1("Encountered EOF before expected!\n");
1536 Status = STATUS_END_OF_FILE;
1537 goto Cleanup;
1538 }
1539
1540 break;
1541 }
1542
1543 // Advance to the next data run
1544 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1545 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1546 if (DataRunOffset != -1)
1547 {
1548 // Normal data run.
1549 DataRunStartLCN = LastLCN + DataRunOffset;
1550 LastLCN = DataRunStartLCN;
1551 }
1552 else
1553 {
1554 // Sparse data run.
1555 DataRunStartLCN = -1;
1556 }
1557 } // end while (Length > 0) [more data to write]
1558
1559 Context->CacheRun = DataRun;
1560 Context->CacheRunOffset = Offset + *RealLengthWritten;
1561 Context->CacheRunStartLCN = DataRunStartLCN;
1562 Context->CacheRunLength = DataRunLength;
1563 Context->CacheRunLastLCN = LastLCN;
1564 Context->CacheRunCurrentOffset = CurrentOffset;
1565
1566 Cleanup:
1567 // TEMPTEMP
1568 if (Context->pRecord->IsNonResident)
1569 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1570
1571 return Status;
1572 }
1573
1574 NTSTATUS
1575 ReadFileRecord(PDEVICE_EXTENSION Vcb,
1576 ULONGLONG index,
1577 PFILE_RECORD_HEADER file)
1578 {
1579 ULONGLONG BytesRead;
1580
1581 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
1582
1583 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
1584 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
1585 {
1586 DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
1587 return STATUS_PARTIAL_COPY;
1588 }
1589
1590 /* Apply update sequence array fixups. */
1591 DPRINT("Sequence number: %u\n", file->SequenceNumber);
1592 return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
1593 }
1594
1595
1596 /**
1597 * Searches a file's parent directory (given the parent's index in the mft)
1598 * for the given file. Upon finding an index entry for that file, updates
1599 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1600 *
1601 * (Most of this code was copied from NtfsFindMftRecord)
1602 */
1603 NTSTATUS
1604 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
1605 ULONGLONG ParentMFTIndex,
1606 PUNICODE_STRING FileName,
1607 BOOLEAN DirSearch,
1608 ULONGLONG NewDataSize,
1609 ULONGLONG NewAllocationSize,
1610 BOOLEAN CaseSensitive)
1611 {
1612 PFILE_RECORD_HEADER MftRecord;
1613 PNTFS_ATTR_CONTEXT IndexRootCtx;
1614 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1615 PCHAR IndexRecord;
1616 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1617 NTSTATUS Status;
1618 ULONG CurrentEntry = 0;
1619
1620 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n",
1621 Vcb,
1622 ParentMFTIndex,
1623 FileName,
1624 DirSearch ? "TRUE" : "FALSE",
1625 NewDataSize,
1626 NewAllocationSize,
1627 CaseSensitive ? "TRUE" : "FALSE");
1628
1629 MftRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
1630 if (MftRecord == NULL)
1631 {
1632 return STATUS_INSUFFICIENT_RESOURCES;
1633 }
1634
1635 Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
1636 if (!NT_SUCCESS(Status))
1637 {
1638 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1639 return Status;
1640 }
1641
1642 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1643 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1644 if (!NT_SUCCESS(Status))
1645 {
1646 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1647 return Status;
1648 }
1649
1650 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1651 if (IndexRecord == NULL)
1652 {
1653 ReleaseAttributeContext(IndexRootCtx);
1654 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1655 return STATUS_INSUFFICIENT_RESOURCES;
1656 }
1657
1658 Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(IndexRootCtx->pRecord));
1659 if (!NT_SUCCESS(Status))
1660 {
1661 DPRINT1("ERROR: Failed to read Index Root!\n");
1662 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1663 ReleaseAttributeContext(IndexRootCtx);
1664 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1665 return Status;
1666 }
1667
1668 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1669 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1670 // Index root is always resident.
1671 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1672
1673 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1674
1675 Status = UpdateIndexEntryFileNameSize(Vcb,
1676 MftRecord,
1677 IndexRecord,
1678 IndexRoot->SizeOfEntry,
1679 IndexEntry,
1680 IndexEntryEnd,
1681 FileName,
1682 &CurrentEntry,
1683 &CurrentEntry,
1684 DirSearch,
1685 NewDataSize,
1686 NewAllocationSize,
1687 CaseSensitive);
1688
1689 if (Status == STATUS_PENDING)
1690 {
1691 // we need to write the index root attribute back to disk
1692 ULONG LengthWritten;
1693 Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten, MftRecord);
1694 if (!NT_SUCCESS(Status))
1695 {
1696 DPRINT1("ERROR: Couldn't update Index Root!\n");
1697 }
1698
1699 }
1700
1701 ReleaseAttributeContext(IndexRootCtx);
1702 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1703 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
1704
1705 return Status;
1706 }
1707
1708 /**
1709 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1710 * proper index entry.
1711 * (Heavily based on BrowseIndexEntries)
1712 */
1713 NTSTATUS
1714 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
1715 PFILE_RECORD_HEADER MftRecord,
1716 PCHAR IndexRecord,
1717 ULONG IndexBlockSize,
1718 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1719 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1720 PUNICODE_STRING FileName,
1721 PULONG StartEntry,
1722 PULONG CurrentEntry,
1723 BOOLEAN DirSearch,
1724 ULONGLONG NewDataSize,
1725 ULONGLONG NewAllocatedSize,
1726 BOOLEAN CaseSensitive)
1727 {
1728 NTSTATUS Status;
1729 ULONG RecordOffset;
1730 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1731 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1732 ULONGLONG IndexAllocationSize;
1733 PINDEX_BUFFER IndexBuffer;
1734
1735 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n",
1736 Vcb,
1737 MftRecord,
1738 IndexRecord,
1739 IndexBlockSize,
1740 FirstEntry,
1741 LastEntry,
1742 FileName,
1743 *StartEntry,
1744 *CurrentEntry,
1745 DirSearch ? "TRUE" : "FALSE",
1746 NewDataSize,
1747 NewAllocatedSize,
1748 CaseSensitive ? "TRUE" : "FALSE");
1749
1750 // find the index entry responsible for the file we're trying to update
1751 IndexEntry = FirstEntry;
1752 while (IndexEntry < LastEntry &&
1753 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1754 {
1755 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > NTFS_FILE_FIRST_USER_FILE &&
1756 *CurrentEntry >= *StartEntry &&
1757 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1758 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
1759 {
1760 *StartEntry = *CurrentEntry;
1761 IndexEntry->FileName.DataSize = NewDataSize;
1762 IndexEntry->FileName.AllocatedSize = NewAllocatedSize;
1763 // indicate that the caller will still need to write the structure to the disk
1764 return STATUS_PENDING;
1765 }
1766
1767 (*CurrentEntry) += 1;
1768 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1769 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1770 }
1771
1772 /* If we're already browsing a subnode */
1773 if (IndexRecord == NULL)
1774 {
1775 return STATUS_OBJECT_PATH_NOT_FOUND;
1776 }
1777
1778 /* If there's no subnode */
1779 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1780 {
1781 return STATUS_OBJECT_PATH_NOT_FOUND;
1782 }
1783
1784 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1785 if (!NT_SUCCESS(Status))
1786 {
1787 DPRINT("Corrupted filesystem!\n");
1788 return Status;
1789 }
1790
1791 IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord);
1792 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1793 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1794 {
1795 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1796 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1797 if (!NT_SUCCESS(Status))
1798 {
1799 break;
1800 }
1801
1802 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1803 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1804 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1805 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1806 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1807 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1808
1809 Status = UpdateIndexEntryFileNameSize(NULL,
1810 NULL,
1811 NULL,
1812 0,
1813 FirstEntry,
1814 LastEntry,
1815 FileName,
1816 StartEntry,
1817 CurrentEntry,
1818 DirSearch,
1819 NewDataSize,
1820 NewAllocatedSize,
1821 CaseSensitive);
1822 if (Status == STATUS_PENDING)
1823 {
1824 // write the index record back to disk
1825 ULONG Written;
1826
1827 // first we need to update the fixup values for the index block
1828 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1829 if (!NT_SUCCESS(Status))
1830 {
1831 DPRINT1("Error: Failed to update fixup sequence array!\n");
1832 break;
1833 }
1834
1835 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written, MftRecord);
1836 if (!NT_SUCCESS(Status))
1837 {
1838 DPRINT1("ERROR Performing write!\n");
1839 break;
1840 }
1841
1842 Status = STATUS_SUCCESS;
1843 break;
1844 }
1845 if (NT_SUCCESS(Status))
1846 {
1847 break;
1848 }
1849 }
1850
1851 ReleaseAttributeContext(IndexAllocationCtx);
1852 return Status;
1853 }
1854
1855 /**
1856 * @name UpdateFileRecord
1857 * @implemented
1858 *
1859 * Writes a file record to the master file table, at a given index.
1860 *
1861 * @param Vcb
1862 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1863 *
1864 * @param MftIndex
1865 * Target index in the master file table to store the file record.
1866 *
1867 * @param FileRecord
1868 * Pointer to the complete file record which will be written to the master file table.
1869 *
1870 * @return
1871 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1872 *
1873 */
1874 NTSTATUS
1875 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
1876 ULONGLONG MftIndex,
1877 PFILE_RECORD_HEADER FileRecord)
1878 {
1879 ULONG BytesWritten;
1880 NTSTATUS Status = STATUS_SUCCESS;
1881
1882 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord);
1883
1884 // Add the fixup array to prepare the data for writing to disk
1885 AddFixupArray(Vcb, &FileRecord->Ntfs);
1886
1887 // write the file record to the master file table
1888 Status = WriteAttribute(Vcb,
1889 Vcb->MFTContext,
1890 MftIndex * Vcb->NtfsInfo.BytesPerFileRecord,
1891 (const PUCHAR)FileRecord,
1892 Vcb->NtfsInfo.BytesPerFileRecord,
1893 &BytesWritten,
1894 FileRecord);
1895
1896 if (!NT_SUCCESS(Status))
1897 {
1898 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
1899 }
1900
1901 // remove the fixup array (so the file record pointer can still be used)
1902 FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs);
1903
1904 return Status;
1905 }
1906
1907
1908 NTSTATUS
1909 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
1910 PNTFS_RECORD_HEADER Record)
1911 {
1912 USHORT *USA;
1913 USHORT USANumber;
1914 USHORT USACount;
1915 USHORT *Block;
1916
1917 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
1918 USANumber = *(USA++);
1919 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
1920 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
1921
1922 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
1923
1924 while (USACount)
1925 {
1926 if (*Block != USANumber)
1927 {
1928 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
1929 return STATUS_UNSUCCESSFUL;
1930 }
1931 *Block = *(USA++);
1932 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
1933 USACount--;
1934 }
1935
1936 return STATUS_SUCCESS;
1937 }
1938
1939 /**
1940 * @name AddNewMftEntry
1941 * @implemented
1942 *
1943 * Adds a file record to the master file table of a given device.
1944 *
1945 * @param FileRecord
1946 * Pointer to a complete file record which will be saved to disk.
1947 *
1948 * @param DeviceExt
1949 * Pointer to the DEVICE_EXTENSION of the target drive.
1950 *
1951 * @param DestinationIndex
1952 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1953 *
1954 * @param CanWait
1955 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
1956 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
1957 *
1958 * @return
1959 * STATUS_SUCCESS on success.
1960 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1961 * to read the attribute.
1962 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1963 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
1964 */
1965 NTSTATUS
1966 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
1967 PDEVICE_EXTENSION DeviceExt,
1968 PULONGLONG DestinationIndex,
1969 BOOLEAN CanWait)
1970 {
1971 NTSTATUS Status = STATUS_SUCCESS;
1972 ULONGLONG MftIndex;
1973 RTL_BITMAP Bitmap;
1974 ULONGLONG BitmapDataSize;
1975 ULONGLONG AttrBytesRead;
1976 PUCHAR BitmapData;
1977 PUCHAR BitmapBuffer;
1978 ULONG LengthWritten;
1979 PNTFS_ATTR_CONTEXT BitmapContext;
1980 LARGE_INTEGER BitmapBits;
1981 UCHAR SystemReservedBits;
1982
1983 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
1984
1985 // First, we have to read the mft's $Bitmap attribute
1986
1987 // Find the attribute
1988 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
1989 if (!NT_SUCCESS(Status))
1990 {
1991 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1992 return Status;
1993 }
1994
1995 // Get size of bitmap
1996 BitmapDataSize = AttributeDataLength(BitmapContext->pRecord);
1997
1998 // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple
1999 // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer
2000 BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS);
2001 if (!BitmapBuffer)
2002 {
2003 ReleaseAttributeContext(BitmapContext);
2004 return STATUS_INSUFFICIENT_RESOURCES;
2005 }
2006 RtlZeroMemory(BitmapBuffer, BitmapDataSize + sizeof(ULONG));
2007
2008 // Get a ULONG-aligned pointer for the bitmap itself
2009 BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG));
2010
2011 // read $Bitmap attribute
2012 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize);
2013
2014 if (AttrBytesRead != BitmapDataSize)
2015 {
2016 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
2017 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2018 ReleaseAttributeContext(BitmapContext);
2019 return STATUS_OBJECT_NAME_NOT_FOUND;
2020 }
2021
2022 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records
2023 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk).
2024 SystemReservedBits = BitmapData[2];
2025 BitmapData[2] = 0xff;
2026
2027 // Calculate bit count
2028 BitmapBits.QuadPart = AttributeDataLength(DeviceExt->MFTContext->pRecord) /
2029 DeviceExt->NtfsInfo.BytesPerFileRecord;
2030 if (BitmapBits.HighPart != 0)
2031 {
2032 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
2033 NtfsGlobalData->EnableWriteSupport = FALSE;
2034 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2035 ReleaseAttributeContext(BitmapContext);
2036 return STATUS_NOT_IMPLEMENTED;
2037 }
2038
2039 // convert buffer into bitmap
2040 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart);
2041
2042 // set next available bit, preferrably after 23rd bit
2043 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
2044 if ((LONG)MftIndex == -1)
2045 {
2046 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
2047
2048 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2049 ReleaseAttributeContext(BitmapContext);
2050
2051 // Couldn't find a free record in the MFT, add some blank records and try again
2052 Status = IncreaseMftSize(DeviceExt, CanWait);
2053 if (!NT_SUCCESS(Status))
2054 {
2055 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
2056 return Status;
2057 }
2058
2059 return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait);
2060 }
2061
2062 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
2063
2064 // update file record with index
2065 FileRecord->MFTRecordNumber = MftIndex;
2066
2067 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
2068
2069 // Restore the system reserved bits
2070 BitmapData[2] = SystemReservedBits;
2071
2072 // write the bitmap back to the MFT's $Bitmap attribute
2073 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord);
2074 if (!NT_SUCCESS(Status))
2075 {
2076 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
2077 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2078 ReleaseAttributeContext(BitmapContext);
2079 return Status;
2080 }
2081
2082 // update the file record (write it to disk)
2083 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
2084
2085 if (!NT_SUCCESS(Status))
2086 {
2087 DPRINT1("ERROR: Unable to write file record!\n");
2088 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2089 ReleaseAttributeContext(BitmapContext);
2090 return Status;
2091 }
2092
2093 *DestinationIndex = MftIndex;
2094
2095 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2096 ReleaseAttributeContext(BitmapContext);
2097
2098 return Status;
2099 }
2100
2101 /**
2102 * @name NtfsAddFilenameToDirectory
2103 * @implemented
2104 *
2105 * Adds a $FILE_NAME attribute to a given directory index.
2106 *
2107 * @param DeviceExt
2108 * Points to the target disk's DEVICE_EXTENSION.
2109 *
2110 * @param DirectoryMftIndex
2111 * Mft index of the parent directory which will receive the file.
2112 *
2113 * @param FileReferenceNumber
2114 * File reference of the file to be added to the directory. This is a combination of the
2115 * Mft index and sequence number.
2116 *
2117 * @param FilenameAttribute
2118 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
2119 *
2120 * @param CaseSensitive
2121 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
2122 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
2123 *
2124 * @return
2125 * STATUS_SUCCESS on success.
2126 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
2127 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
2128 *
2129 * @remarks
2130 * WIP - Can only support a few files in a directory.
2131 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
2132 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
2133 * get both attributes added to its parent directory.
2134 */
2135 NTSTATUS
2136 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
2137 ULONGLONG DirectoryMftIndex,
2138 ULONGLONG FileReferenceNumber,
2139 PFILENAME_ATTRIBUTE FilenameAttribute,
2140 BOOLEAN CaseSensitive)
2141 {
2142 NTSTATUS Status = STATUS_SUCCESS;
2143 PFILE_RECORD_HEADER ParentFileRecord;
2144 PNTFS_ATTR_CONTEXT IndexRootContext;
2145 PINDEX_ROOT_ATTRIBUTE I30IndexRoot;
2146 ULONG IndexRootOffset;
2147 ULONGLONG I30IndexRootLength;
2148 ULONG LengthWritten;
2149 PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
2150 ULONG AttributeLength;
2151 PNTFS_ATTR_RECORD NextAttribute;
2152 PB_TREE NewTree;
2153 ULONG BtreeIndexLength;
2154 ULONG MaxIndexRootSize;
2155 PB_TREE_KEY NewLeftKey;
2156 PB_TREE_FILENAME_NODE NewRightHandNode;
2157 LARGE_INTEGER MinIndexRootSize;
2158 ULONG NewMaxIndexRootSize;
2159 ULONG NodeSize;
2160
2161 // Allocate memory for the parent directory
2162 ParentFileRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
2163 if (!ParentFileRecord)
2164 {
2165 DPRINT1("ERROR: Couldn't allocate memory for file record!\n");
2166 return STATUS_INSUFFICIENT_RESOURCES;
2167 }
2168
2169 // Open the parent directory
2170 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2171 if (!NT_SUCCESS(Status))
2172 {
2173 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2174 DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n",
2175 DirectoryMftIndex);
2176 return Status;
2177 }
2178
2179 #ifndef NDEBUG
2180 DPRINT1("Dumping old parent file record:\n");
2181 NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
2182 #endif
2183
2184 // Find the index root attribute for the directory
2185 Status = FindAttribute(DeviceExt,
2186 ParentFileRecord,
2187 AttributeIndexRoot,
2188 L"$I30",
2189 4,
2190 &IndexRootContext,
2191 &IndexRootOffset);
2192 if (!NT_SUCCESS(Status))
2193 {
2194 DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n",
2195 DirectoryMftIndex);
2196 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2197 return Status;
2198 }
2199
2200 // Find the maximum index size given what the file record can hold
2201 // First, find the max index size assuming index root is the last attribute
2202 MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
2203 - IndexRootOffset // Subtract the length of everything that comes before index root
2204 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
2205 - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header
2206 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding
2207
2208 // Are there attributes after this one?
2209 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
2210 if (NextAttribute->Type != AttributeEnd)
2211 {
2212 // Find the length of all attributes after this one, not counting the end marker
2213 ULONG LengthOfAttributes = 0;
2214 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
2215 while (CurrentAttribute->Type != AttributeEnd)
2216 {
2217 LengthOfAttributes += CurrentAttribute->Length;
2218 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
2219 }
2220
2221 // Leave room for the existing attributes
2222 MaxIndexRootSize -= LengthOfAttributes;
2223 }
2224
2225 // Allocate memory for the index root data
2226 I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord);
2227 I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS);
2228 if (!I30IndexRoot)
2229 {
2230 DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n");
2231 ReleaseAttributeContext(IndexRootContext);
2232 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2233 return STATUS_INSUFFICIENT_RESOURCES;
2234 }
2235
2236 // Read the Index Root
2237 Status = ReadAttribute(DeviceExt, IndexRootContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength);
2238 if (!NT_SUCCESS(Status))
2239 {
2240 DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex);
2241 ReleaseAttributeContext(IndexRootContext);
2242 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2243 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2244 return Status;
2245 }
2246
2247 // Convert the index to a B*Tree
2248 Status = CreateBTreeFromIndex(DeviceExt,
2249 ParentFileRecord,
2250 IndexRootContext,
2251 I30IndexRoot,
2252 &NewTree);
2253 if (!NT_SUCCESS(Status))
2254 {
2255 DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
2256 ReleaseAttributeContext(IndexRootContext);
2257 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2258 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2259 return Status;
2260 }
2261
2262 #ifndef NDEBUG
2263 DumpBTree(NewTree);
2264 #endif
2265
2266 // Insert the key for the file we're adding
2267 Status = NtfsInsertKey(NewTree,
2268 FileReferenceNumber,
2269 FilenameAttribute,
2270 NewTree->RootNode,
2271 CaseSensitive,
2272 MaxIndexRootSize,
2273 I30IndexRoot->SizeOfEntry,
2274 &NewLeftKey,
2275 &NewRightHandNode);
2276 if (!NT_SUCCESS(Status))
2277 {
2278 DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
2279 DestroyBTree(NewTree);
2280 ReleaseAttributeContext(IndexRootContext);
2281 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2282 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2283 return Status;
2284 }
2285
2286 #ifndef NDEBUG
2287 DumpBTree(NewTree);
2288 #endif
2289
2290 // The root node can't be split
2291 ASSERT(NewLeftKey == NULL);
2292 ASSERT(NewRightHandNode == NULL);
2293
2294 // Convert B*Tree back to Index
2295
2296 // Updating the index allocation can change the size available for the index root,
2297 // And if the index root is demoted, the index allocation will need to be updated again,
2298 // which may change the size available for index root... etc.
2299 // My solution is to decrease index root to the size it would be if it was demoted,
2300 // then UpdateIndexAllocation will have an accurate representation of the maximum space
2301 // it can use in the file record. There's still a chance that the act of allocating an
2302 // index node after demoting the index root will increase the size of the file record beyond
2303 // it's limit, but if that happens, an attribute-list will most definitely be needed.
2304 // This a bit hacky, but it seems to be functional.
2305
2306 // Calculate the minimum size of the index root attribute, considering one dummy key and one VCN
2307 MinIndexRootSize.QuadPart = sizeof(INDEX_ROOT_ATTRIBUTE) // size of the index root headers
2308 + 0x18; // Size of dummy key with a VCN for a subnode
2309 ASSERT(MinIndexRootSize.QuadPart % ATTR_RECORD_ALIGNMENT == 0);
2310
2311 // Temporarily shrink the index root to it's minimal size
2312 AttributeLength = MinIndexRootSize.LowPart;
2313 AttributeLength += sizeof(INDEX_ROOT_ATTRIBUTE);
2314
2315
2316 // FIXME: IndexRoot will probably be invalid until we're finished. If we fail before we finish, the directory will probably be toast.
2317 // The potential for catastrophic data-loss exists!!! :)
2318
2319 // Update the length of the attribute in the file record of the parent directory
2320 Status = InternalSetResidentAttributeLength(DeviceExt,
2321 IndexRootContext,
2322 ParentFileRecord,
2323 IndexRootOffset,
2324 AttributeLength);
2325 if (!NT_SUCCESS(Status))
2326 {
2327 DPRINT1("ERROR: Unable to set length of index root!\n");
2328 DestroyBTree(NewTree);
2329 ReleaseAttributeContext(IndexRootContext);
2330 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2331 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2332 return Status;
2333 }
2334
2335 // Update the index allocation
2336 Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
2337 if (!NT_SUCCESS(Status))
2338 {
2339 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2340 DestroyBTree(NewTree);
2341 ReleaseAttributeContext(IndexRootContext);
2342 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2343 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2344 return Status;
2345 }
2346
2347 #ifndef NDEBUG
2348 DPRINT1("Index Allocation updated\n");
2349 DumpBTree(NewTree);
2350 #endif
2351
2352 // Find the maximum index root size given what the file record can hold
2353 // First, find the max index size assuming index root is the last attribute
2354 NewMaxIndexRootSize =
2355 DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
2356 - IndexRootOffset // Subtract the length of everything that comes before index root
2357 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
2358 - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header
2359 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding
2360
2361 // Are there attributes after this one?
2362 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
2363 if (NextAttribute->Type != AttributeEnd)
2364 {
2365 // Find the length of all attributes after this one, not counting the end marker
2366 ULONG LengthOfAttributes = 0;
2367 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
2368 while (CurrentAttribute->Type != AttributeEnd)
2369 {
2370 LengthOfAttributes += CurrentAttribute->Length;
2371 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
2372 }
2373
2374 // Leave room for the existing attributes
2375 NewMaxIndexRootSize -= LengthOfAttributes;
2376 }
2377
2378 // The index allocation and index bitmap may have grown, leaving less room for the index root,
2379 // so now we need to double-check that index root isn't too large
2380 NodeSize = GetSizeOfIndexEntries(NewTree->RootNode);
2381 if (NodeSize > NewMaxIndexRootSize)
2382 {
2383 DPRINT1("Demoting index root.\nNodeSize: 0x%lx\nNewMaxIndexRootSize: 0x%lx\n", NodeSize, NewMaxIndexRootSize);
2384
2385 Status = DemoteBTreeRoot(NewTree);
2386 if (!NT_SUCCESS(Status))
2387 {
2388 DPRINT1("ERROR: Failed to demote index root!\n");
2389 DestroyBTree(NewTree);
2390 ReleaseAttributeContext(IndexRootContext);
2391 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2392 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2393 return Status;
2394 }
2395
2396 // We need to update the index allocation once more
2397 Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
2398 if (!NT_SUCCESS(Status))
2399 {
2400 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2401 DestroyBTree(NewTree);
2402 ReleaseAttributeContext(IndexRootContext);
2403 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2404 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2405 return Status;
2406 }
2407
2408 // re-recalculate max size of index root
2409 NewMaxIndexRootSize =
2410 // Find the maximum index size given what the file record can hold
2411 // First, find the max index size assuming index root is the last attribute
2412 DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
2413 - IndexRootOffset // Subtract the length of everything that comes before index root
2414 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
2415 - sizeof(INDEX_ROOT_ATTRIBUTE) // Subtract the length of the index root header
2416 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding
2417
2418 // Are there attributes after this one?
2419 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
2420 if (NextAttribute->Type != AttributeEnd)
2421 {
2422 // Find the length of all attributes after this one, not counting the end marker
2423 ULONG LengthOfAttributes = 0;
2424 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
2425 while (CurrentAttribute->Type != AttributeEnd)
2426 {
2427 LengthOfAttributes += CurrentAttribute->Length;
2428 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
2429 }
2430
2431 // Leave room for the existing attributes
2432 NewMaxIndexRootSize -= LengthOfAttributes;
2433 }
2434
2435
2436 }
2437
2438 // Create the Index Root from the B*Tree
2439 Status = CreateIndexRootFromBTree(DeviceExt, NewTree, NewMaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength);
2440 if (!NT_SUCCESS(Status))
2441 {
2442 DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
2443 DestroyBTree(NewTree);
2444 ReleaseAttributeContext(IndexRootContext);
2445 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2446 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2447 return Status;
2448 }
2449
2450 // We're done with the B-Tree now
2451 DestroyBTree(NewTree);
2452
2453 // Write back the new index root attribute to the parent directory file record
2454
2455 // First, we need to resize the attribute.
2456 // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
2457 // We can't set the size as we normally would, because $INDEX_ROOT must always be resident.
2458 AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
2459
2460 if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength)
2461 {
2462 // Update the length of the attribute in the file record of the parent directory
2463 Status = InternalSetResidentAttributeLength(DeviceExt,
2464 IndexRootContext,
2465 ParentFileRecord,
2466 IndexRootOffset,
2467 AttributeLength);
2468 if (!NT_SUCCESS(Status))
2469 {
2470 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2471 ReleaseAttributeContext(IndexRootContext);
2472 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2473 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2474 DPRINT1("ERROR: Unable to set resident attribute length!\n");
2475 return Status;
2476 }
2477
2478 }
2479
2480 NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord);
2481
2482 Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2483 if (!NT_SUCCESS(Status))
2484 {
2485 DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex);
2486 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2487 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2488 ReleaseAttributeContext(IndexRootContext);
2489 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2490 return Status;
2491 }
2492
2493 // Write the new index root to disk
2494 Status = WriteAttribute(DeviceExt,
2495 IndexRootContext,
2496 0,
2497 (PUCHAR)NewIndexRoot,
2498 AttributeLength,
2499 &LengthWritten,
2500 ParentFileRecord);
2501 if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength)
2502 {
2503 DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
2504 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2505 ReleaseAttributeContext(IndexRootContext);
2506 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2507 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2508 return Status;
2509 }
2510
2511 // re-read the parent file record, so we can dump it
2512 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2513 if (!NT_SUCCESS(Status))
2514 {
2515 DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n");
2516 }
2517 else
2518 {
2519 #ifndef NDEBUG
2520 DPRINT1("Dumping new B-Tree:\n");
2521
2522 Status = CreateBTreeFromIndex(DeviceExt, ParentFileRecord, IndexRootContext, NewIndexRoot, &NewTree);
2523 if (!NT_SUCCESS(Status))
2524 {
2525 DPRINT1("ERROR: Couldn't re-create b-tree\n");
2526 return Status;
2527 }
2528
2529 DumpBTree(NewTree);
2530
2531 DestroyBTree(NewTree);
2532
2533 NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
2534 #endif
2535 }
2536
2537 // Cleanup
2538 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2539 ReleaseAttributeContext(IndexRootContext);
2540 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2541 ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, ParentFileRecord);
2542
2543 return Status;
2544 }
2545
2546 NTSTATUS
2547 AddFixupArray(PDEVICE_EXTENSION Vcb,
2548 PNTFS_RECORD_HEADER Record)
2549 {
2550 USHORT *pShortToFixUp;
2551 ULONG ArrayEntryCount = Record->UsaCount - 1;
2552 ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2;
2553 ULONG i;
2554
2555 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
2556
2557 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
2558
2559 fixupArray->USN++;
2560
2561 for (i = 0; i < ArrayEntryCount; i++)
2562 {
2563 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
2564
2565 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
2566 fixupArray->Array[i] = *pShortToFixUp;
2567 *pShortToFixUp = fixupArray->USN;
2568 Offset += Vcb->NtfsInfo.BytesPerSector;
2569 }
2570
2571 return STATUS_SUCCESS;
2572 }
2573
2574 NTSTATUS
2575 ReadLCN(PDEVICE_EXTENSION Vcb,
2576 ULONGLONG lcn,
2577 ULONG count,
2578 PVOID buffer)
2579 {
2580 LARGE_INTEGER DiskSector;
2581
2582 DiskSector.QuadPart = lcn;
2583
2584 return NtfsReadSectors(Vcb->StorageDevice,
2585 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
2586 count * Vcb->NtfsInfo.SectorsPerCluster,
2587 Vcb->NtfsInfo.BytesPerSector,
2588 buffer,
2589 FALSE);
2590 }
2591
2592
2593 BOOLEAN
2594 CompareFileName(PUNICODE_STRING FileName,
2595 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
2596 BOOLEAN DirSearch,
2597 BOOLEAN CaseSensitive)
2598 {
2599 BOOLEAN Ret, Alloc = FALSE;
2600 UNICODE_STRING EntryName;
2601
2602 EntryName.Buffer = IndexEntry->FileName.Name;
2603 EntryName.Length =
2604 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
2605
2606 if (DirSearch)
2607 {
2608 UNICODE_STRING IntFileName;
2609 if (!CaseSensitive)
2610 {
2611 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
2612 Alloc = TRUE;
2613 }
2614 else
2615 {
2616 IntFileName = *FileName;
2617 }
2618
2619 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL);
2620
2621 if (Alloc)
2622 {
2623 RtlFreeUnicodeString(&IntFileName);
2624 }
2625
2626 return Ret;
2627 }
2628 else
2629 {
2630 return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0);
2631 }
2632 }
2633
2634 /**
2635 * @name UpdateMftMirror
2636 * @implemented
2637 *
2638 * Backs-up the first ~4 master file table entries to the $MFTMirr file.
2639 *
2640 * @param Vcb
2641 * Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated.
2642 *
2643 * @return
2644
2645 * STATUS_SUCCESS on success.
2646 * STATUS_INSUFFICIENT_RESOURCES if an allocation failed.
2647 * STATUS_UNSUCCESSFUL if we couldn't read the master file table.
2648 *
2649 * @remarks
2650 * NTFS maintains up-to-date copies of the first several mft entries in the $MFTMirr file. Usually, the first 4 file
2651 * records from the mft are stored. The exact number of entries is determined by the size of $MFTMirr's $DATA.
2652 * If $MFTMirr is not up-to-date, chkdsk will reject every change it can find prior to when $MFTMirr was last updated.
2653 * Therefore, it's recommended to call this function if the volume changes considerably. For instance, IncreaseMftSize()
2654 * relies on this function to keep chkdsk from deleting the mft entries it creates. Note that under most instances, creating
2655 * or deleting a file will not affect the first ~four mft entries, and so will not require updating the mft mirror.
2656 */
2657 NTSTATUS
2658 UpdateMftMirror(PNTFS_VCB Vcb)
2659 {
2660 PFILE_RECORD_HEADER MirrorFileRecord;
2661 PNTFS_ATTR_CONTEXT MirrDataContext;
2662 PNTFS_ATTR_CONTEXT MftDataContext;
2663 PCHAR DataBuffer;
2664 ULONGLONG DataLength;
2665 NTSTATUS Status;
2666 ULONG BytesRead;
2667 ULONG LengthWritten;
2668
2669 // Allocate memory for the Mft mirror file record
2670 MirrorFileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
2671 if (!MirrorFileRecord)
2672 {
2673 DPRINT1("Error: Failed to allocate memory for $MFTMirr!\n");
2674 return STATUS_INSUFFICIENT_RESOURCES;
2675 }
2676
2677 // Read the Mft Mirror file record
2678 Status = ReadFileRecord(Vcb, NTFS_FILE_MFTMIRR, MirrorFileRecord);
2679 if (!NT_SUCCESS(Status))
2680 {
2681 DPRINT1("ERROR: Failed to read $MFTMirr!\n");
2682 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2683 return Status;
2684 }
2685
2686 // Find the $DATA attribute of $MFTMirr
2687 Status = FindAttribute(Vcb, MirrorFileRecord, AttributeData, L"", 0, &MirrDataContext, NULL);
2688 if (!NT_SUCCESS(Status))
2689 {
2690 DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
2691 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2692 return Status;
2693 }
2694
2695 // Find the $DATA attribute of $MFT
2696 Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeData, L"", 0, &MftDataContext, NULL);
2697 if (!NT_SUCCESS(Status))
2698 {
2699 DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
2700 ReleaseAttributeContext(MirrDataContext);
2701 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2702 return Status;
2703 }
2704
2705 // Get the size of the mirror's $DATA attribute
2706 DataLength = AttributeDataLength(MirrDataContext->pRecord);
2707
2708 ASSERT(DataLength % Vcb->NtfsInfo.BytesPerFileRecord == 0);
2709
2710 // Create buffer for the mirror's $DATA attribute
2711 DataBuffer = ExAllocatePoolWithTag(NonPagedPool, DataLength, TAG_NTFS);
2712 if (!DataBuffer)
2713 {
2714 DPRINT1("Error: Couldn't allocate memory for $DATA buffer!\n");
2715 ReleaseAttributeContext(MftDataContext);
2716 ReleaseAttributeContext(MirrDataContext);
2717 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2718 return STATUS_INSUFFICIENT_RESOURCES;
2719 }
2720
2721 ASSERT(DataLength < ULONG_MAX);
2722
2723 // Back up the first several entries of the Mft's $DATA Attribute
2724 BytesRead = ReadAttribute(Vcb, MftDataContext, 0, DataBuffer, (ULONG)DataLength);
2725 if (BytesRead != (ULONG)DataLength)
2726 {
2727 DPRINT1("Error: Failed to read $DATA for $MFTMirr!\n");
2728 ReleaseAttributeContext(MftDataContext);
2729 ReleaseAttributeContext(MirrDataContext);
2730 ExFreePoolWithTag(DataBuffer, TAG_NTFS);
2731 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2732 return STATUS_UNSUCCESSFUL;
2733 }
2734
2735 // Write the mirror's $DATA attribute
2736 Status = WriteAttribute(Vcb,
2737 MirrDataContext,
2738 0,
2739 (PUCHAR)DataBuffer,
2740 DataLength,
2741 &LengthWritten,
2742 MirrorFileRecord);
2743 if (!NT_SUCCESS(Status))
2744 {
2745 DPRINT1("ERROR: Failed to write $DATA attribute of $MFTMirr!\n");
2746 }
2747
2748 // Cleanup
2749 ReleaseAttributeContext(MftDataContext);
2750 ReleaseAttributeContext(MirrDataContext);
2751 ExFreePoolWithTag(DataBuffer, TAG_NTFS);
2752 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MirrorFileRecord);
2753
2754 return Status;
2755 }
2756
2757 #if 0
2758 static
2759 VOID
2760 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
2761 {
2762 DPRINT1("Entry: %p\n", IndexEntry);
2763 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
2764 DPRINT1("\tLength: %u\n", IndexEntry->Length);
2765 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
2766 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
2767 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
2768 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
2769 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
2770 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
2771 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
2772 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
2773 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
2774 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
2775 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
2776 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
2777 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
2778 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
2779 }
2780 #endif
2781
2782 NTSTATUS
2783 BrowseSubNodeIndexEntries(PNTFS_VCB Vcb,
2784 PFILE_RECORD_HEADER MftRecord,
2785 ULONG IndexBlockSize,
2786 PUNICODE_STRING FileName,
2787 PNTFS_ATTR_CONTEXT IndexAllocationContext,
2788 PRTL_BITMAP Bitmap,
2789 ULONGLONG VCN,
2790 PULONG StartEntry,
2791 PULONG CurrentEntry,
2792 BOOLEAN DirSearch,
2793 BOOLEAN CaseSensitive,
2794 ULONGLONG *OutMFTIndex)
2795 {
2796 PINDEX_BUFFER IndexRecord;
2797 ULONGLONG Offset;
2798 ULONG BytesRead;
2799 PINDEX_ENTRY_ATTRIBUTE FirstEntry;
2800 PINDEX_ENTRY_ATTRIBUTE LastEntry;
2801 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
2802 ULONG NodeNumber;
2803 NTSTATUS Status;
2804
2805 DPRINT("BrowseSubNodeIndexEntries(%p, %p, %lu, %wZ, %p, %p, %I64d, %lu, %lu, %s, %s, %p)\n",
2806 Vcb,
2807 MftRecord,
2808 IndexBlockSize,
2809 FileName,
2810 IndexAllocationContext,
2811 Bitmap,
2812 VCN,
2813 *StartEntry,
2814 *CurrentEntry,
2815 "FALSE",
2816 DirSearch ? "TRUE" : "FALSE",
2817 CaseSensitive ? "TRUE" : "FALSE",
2818 OutMFTIndex);
2819
2820 // Calculate node number as VCN / Clusters per index record
2821 NodeNumber = VCN / (Vcb->NtfsInfo.BytesPerIndexRecord / Vcb->NtfsInfo.BytesPerCluster);
2822
2823 // Is the bit for this node clear in the bitmap?
2824 if (!RtlCheckBit(Bitmap, NodeNumber))
2825 {
2826 DPRINT1("File system corruption detected, node with VCN %I64u is marked as deleted.\n", VCN);
2827 return STATUS_DATA_ERROR;
2828 }
2829
2830 // Allocate memory for the index record
2831 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, IndexBlockSize, TAG_NTFS);
2832 if (!IndexRecord)
2833 {
2834 DPRINT1("Unable to allocate memory for index record!\n");
2835 return STATUS_INSUFFICIENT_RESOURCES;
2836 }
2837
2838 // Calculate offset of index record
2839 Offset = VCN * Vcb->NtfsInfo.BytesPerCluster;
2840
2841 // Read the index record
2842 BytesRead = ReadAttribute(Vcb, IndexAllocationContext, Offset, (PCHAR)IndexRecord, IndexBlockSize);
2843 if (BytesRead != IndexBlockSize)
2844 {
2845 DPRINT1("Unable to read index record!\n");
2846 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2847 return STATUS_UNSUCCESSFUL;
2848 }
2849
2850 // Assert that we're dealing with an index record here
2851 ASSERT(IndexRecord->Ntfs.Type == NRH_INDX_TYPE);
2852
2853 // Apply the fixup array to the index record
2854 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
2855 if (!NT_SUCCESS(Status))
2856 {
2857 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2858 DPRINT1("Failed to apply fixup array!\n");
2859 return Status;
2860 }
2861
2862 ASSERT(IndexRecord->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
2863 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.FirstEntryOffset);
2864 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexRecord->Header + IndexRecord->Header.TotalSizeOfEntries);
2865 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexRecord + IndexBlockSize));
2866
2867 // Loop through all Index Entries of index, starting with FirstEntry
2868 IndexEntry = FirstEntry;
2869 while (IndexEntry <= LastEntry)
2870 {
2871 // Does IndexEntry have a sub-node?
2872 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
2873 {
2874 if (!(IndexRecord->Header.Flags & INDEX_NODE_LARGE) || !IndexAllocationContext)
2875 {
2876 DPRINT1("Filesystem corruption detected!\n");
2877 }
2878 else
2879 {
2880 Status = BrowseSubNodeIndexEntries(Vcb,
2881 MftRecord,
2882 IndexBlockSize,
2883 FileName,
2884 IndexAllocationContext,
2885 Bitmap,
2886 GetIndexEntryVCN(IndexEntry),
2887 StartEntry,
2888 CurrentEntry,
2889 DirSearch,
2890 CaseSensitive,
2891 OutMFTIndex);
2892 if (NT_SUCCESS(Status))
2893 {
2894 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2895 return Status;
2896 }
2897 }
2898 }
2899
2900 // Are we done?
2901 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END)
2902 break;
2903
2904 // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria
2905 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE &&
2906 *CurrentEntry >= *StartEntry &&
2907 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
2908 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
2909 {
2910 *StartEntry = *CurrentEntry;
2911 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
2912 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2913 return STATUS_SUCCESS;
2914 }
2915
2916 // Advance to the next index entry
2917 (*CurrentEntry) += 1;
2918 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
2919 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
2920 }
2921
2922 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2923
2924 return STATUS_OBJECT_PATH_NOT_FOUND;
2925 }
2926
2927 NTSTATUS
2928 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
2929 PFILE_RECORD_HEADER MftRecord,
2930 PINDEX_ROOT_ATTRIBUTE IndexRecord,
2931 ULONG IndexBlockSize,
2932 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
2933 PINDEX_ENTRY_ATTRIBUTE LastEntry,
2934 PUNICODE_STRING FileName,
2935 PULONG StartEntry,
2936 PULONG CurrentEntry,
2937 BOOLEAN DirSearch,
2938 BOOLEAN CaseSensitive,
2939 ULONGLONG *OutMFTIndex)
2940 {
2941 NTSTATUS Status;
2942 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
2943 PNTFS_ATTR_CONTEXT IndexAllocationContext;
2944 PNTFS_ATTR_CONTEXT BitmapContext;
2945 PCHAR *BitmapMem;
2946 ULONG *BitmapPtr;
2947 RTL_BITMAP Bitmap;
2948
2949 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
2950 Vcb,
2951 MftRecord,
2952 IndexRecord,
2953 IndexBlockSize,
2954 FirstEntry,
2955 LastEntry,
2956 FileName,
2957 *StartEntry,
2958 *CurrentEntry,
2959 DirSearch ? "TRUE" : "FALSE",
2960 CaseSensitive ? "TRUE" : "FALSE",
2961 OutMFTIndex);
2962
2963 // Find the $I30 index allocation, if there is one
2964 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationContext, NULL);
2965 if (NT_SUCCESS(Status))
2966 {
2967 ULONGLONG BitmapLength;
2968 // Find the bitmap attribute for the index
2969 Status = FindAttribute(Vcb, MftRecord, AttributeBitmap, L"$I30", 4, &BitmapContext, NULL);
2970 if (!NT_SUCCESS(Status))
2971 {
2972 DPRINT1("Potential file system corruption detected!\n");
2973 ReleaseAttributeContext(IndexAllocationContext);
2974 return Status;
2975 }
2976
2977 // Get the length of the bitmap attribute
2978 BitmapLength = AttributeDataLength(BitmapContext->pRecord);
2979
2980 // Allocate memory for the bitmap, including some padding; RtlInitializeBitmap() wants a pointer
2981 // that's ULONG-aligned, and it wants the size of the memory allocated for it to be a ULONG-multiple.
2982 BitmapMem = ExAllocatePoolWithTag(NonPagedPool, BitmapLength + sizeof(ULONG), TAG_NTFS);
2983 if (!BitmapMem)
2984 {
2985 DPRINT1("Error: failed to allocate bitmap!");
2986 ReleaseAttributeContext(BitmapContext);
2987 ReleaseAttributeContext(IndexAllocationContext);
2988 return STATUS_INSUFFICIENT_RESOURCES;
2989 }
2990
2991 RtlZeroMemory(BitmapMem, BitmapLength + sizeof(ULONG));
2992
2993 // RtlInitializeBitmap() wants a pointer that's ULONG-aligned.
2994 BitmapPtr = (PULONG)ALIGN_UP_BY((ULONG_PTR)BitmapMem, sizeof(ULONG));
2995
2996 // Read the existing bitmap data
2997 Status = ReadAttribute(Vcb, BitmapContext, 0, (PCHAR)BitmapPtr, BitmapLength);
2998 if (!NT_SUCCESS(Status))
2999 {
3000 DPRINT1("ERROR: Failed to read bitmap attribute!\n");
3001 ExFreePoolWithTag(BitmapMem, TAG_NTFS);
3002 ReleaseAttributeContext(BitmapContext);
3003 ReleaseAttributeContext(IndexAllocationContext);
3004 return Status;
3005 }
3006
3007 // Initialize bitmap
3008 RtlInitializeBitMap(&Bitmap, BitmapPtr, BitmapLength * 8);
3009 }
3010 else
3011 {
3012 // Couldn't find an index allocation
3013 IndexAllocationContext = NULL;
3014 }
3015
3016
3017 // Loop through all Index Entries of index, starting with FirstEntry
3018 IndexEntry = FirstEntry;
3019 while (IndexEntry <= LastEntry)
3020 {
3021 // Does IndexEntry have a sub-node?
3022 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE)
3023 {
3024 if (!(IndexRecord->Header.Flags & INDEX_ROOT_LARGE) || !IndexAllocationContext)
3025 {
3026 DPRINT1("Filesystem corruption detected!\n");
3027 }
3028 else
3029 {
3030 Status = BrowseSubNodeIndexEntries(Vcb,
3031 MftRecord,
3032 IndexBlockSize,
3033 FileName,
3034 IndexAllocationContext,
3035 &Bitmap,
3036 GetIndexEntryVCN(IndexEntry),
3037 StartEntry,
3038 CurrentEntry,
3039 DirSearch,
3040 CaseSensitive,
3041 OutMFTIndex);
3042 if (NT_SUCCESS(Status))
3043 {
3044 ExFreePoolWithTag(BitmapMem, TAG_NTFS);
3045 ReleaseAttributeContext(BitmapContext);
3046 ReleaseAttributeContext(IndexAllocationContext);
3047 return Status;
3048 }
3049 }
3050 }
3051
3052 // Are we done?
3053 if (IndexEntry->Flags & NTFS_INDEX_ENTRY_END)
3054 break;
3055
3056 // If we've found a file whose index is greater than or equal to StartEntry that matches the search criteria
3057 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE &&
3058 *CurrentEntry >= *StartEntry &&
3059 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
3060 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
3061 {
3062 *StartEntry = *CurrentEntry;
3063 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
3064 if (IndexAllocationContext)
3065 {
3066 ExFreePoolWithTag(BitmapMem, TAG_NTFS);
3067 ReleaseAttributeContext(BitmapContext);
3068 ReleaseAttributeContext(IndexAllocationContext);
3069 }
3070 return STATUS_SUCCESS;
3071 }
3072
3073 // Advance to the next index entry
3074 (*CurrentEntry) += 1;
3075 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
3076 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
3077 }
3078
3079 if (IndexAllocationContext)
3080 {
3081 ExFreePoolWithTag(BitmapMem, TAG_NTFS);
3082 ReleaseAttributeContext(BitmapContext);
3083 ReleaseAttributeContext(IndexAllocationContext);
3084 }
3085
3086 return STATUS_OBJECT_PATH_NOT_FOUND;
3087 }
3088
3089 NTSTATUS
3090 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
3091 ULONGLONG MFTIndex,
3092 PUNICODE_STRING FileName,
3093 PULONG FirstEntry,
3094 BOOLEAN DirSearch,
3095 BOOLEAN CaseSensitive,
3096 ULONGLONG *OutMFTIndex)
3097 {
3098 PFILE_RECORD_HEADER MftRecord;
3099 PNTFS_ATTR_CONTEXT IndexRootCtx;
3100 PINDEX_ROOT_ATTRIBUTE IndexRoot;
3101 PCHAR IndexRecord;
3102 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
3103 NTSTATUS Status;
3104 ULONG CurrentEntry = 0;
3105
3106 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n",
3107 Vcb,
3108 MFTIndex,
3109 FileName,
3110 *FirstEntry,
3111 DirSearch ? "TRUE" : "FALSE",
3112 CaseSensitive ? "TRUE" : "FALSE",
3113 OutMFTIndex);
3114
3115 MftRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
3116 if (MftRecord == NULL)
3117 {
3118 return STATUS_INSUFFICIENT_RESOURCES;
3119 }
3120
3121 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
3122 if (!NT_SUCCESS(Status))
3123 {
3124 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
3125 return Status;
3126 }
3127
3128 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
3129 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
3130 if (!NT_SUCCESS(Status))
3131 {
3132 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
3133 return Status;
3134 }
3135
3136 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
3137 if (IndexRecord == NULL)
3138 {
3139 ReleaseAttributeContext(IndexRootCtx);
3140 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
3141 return STATUS_INSUFFICIENT_RESOURCES;
3142 }
3143
3144 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
3145 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
3146 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
3147 /* Index root is always resident. */
3148 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
3149 ReleaseAttributeContext(IndexRootCtx);
3150
3151 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
3152
3153 Status = BrowseIndexEntries(Vcb,
3154 MftRecord,
3155 (PINDEX_ROOT_ATTRIBUTE)IndexRecord,
3156 IndexRoot->SizeOfEntry,
3157 IndexEntry,
3158 IndexEntryEnd,
3159 FileName,
3160 FirstEntry,
3161 &CurrentEntry,
3162 DirSearch,
3163 CaseSensitive,
3164 OutMFTIndex);
3165
3166 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
3167 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, MftRecord);
3168
3169 return Status;
3170 }
3171
3172 NTSTATUS
3173 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
3174 PUNICODE_STRING PathName,
3175 BOOLEAN CaseSensitive,
3176 PFILE_RECORD_HEADER *FileRecord,
3177 PULONGLONG MFTIndex,
3178 ULONGLONG CurrentMFTIndex)
3179 {
3180 UNICODE_STRING Current, Remaining;
3181 NTSTATUS Status;
3182 ULONG FirstEntry = 0;
3183
3184 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
3185 Vcb,
3186 PathName,
3187 CaseSensitive ? "TRUE" : "FALSE",
3188 FileRecord,
3189 MFTIndex,
3190 CurrentMFTIndex);
3191
3192 FsRtlDissectName(*PathName, &Current, &Remaining);
3193
3194 while (Current.Length != 0)
3195 {
3196 DPRINT("Current: %wZ\n", &Current);
3197
3198 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex);
3199 if (!NT_SUCCESS(Status))
3200 {
3201 return Status;
3202 }
3203
3204 if (Remaining.Length == 0)
3205 break;
3206
3207 FsRtlDissectName(Current, &Current, &Remaining);
3208 }
3209
3210 *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
3211 if (*FileRecord == NULL)
3212 {
3213 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
3214 return STATUS_INSUFFICIENT_RESOURCES;
3215 }
3216
3217 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
3218 if (!NT_SUCCESS(Status))
3219 {
3220 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
3221 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord);
3222 return Status;
3223 }
3224
3225 *MFTIndex = CurrentMFTIndex;
3226
3227 return STATUS_SUCCESS;
3228 }
3229
3230 NTSTATUS
3231 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
3232 PUNICODE_STRING PathName,
3233 BOOLEAN CaseSensitive,
3234 PFILE_RECORD_HEADER *FileRecord,
3235 PULONGLONG MFTIndex)
3236 {
3237 return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT);
3238 }
3239
3240 void
3241 NtfsDumpData(ULONG_PTR Buffer, ULONG Length)
3242 {
3243 ULONG i, j;
3244
3245 // dump binary data, 8 bytes at a time
3246 for (i = 0; i < Length; i += 8)
3247 {
3248 // display current offset, in hex
3249 DbgPrint("\t%03x\t", i);
3250
3251 // display hex value of each of the next 8 bytes
3252 for (j = 0; j < 8; j++)
3253 DbgPrint("%02x ", *(PUCHAR)(Buffer + i + j));
3254 DbgPrint("\n");
3255 }
3256 }
3257
3258 /**
3259 * @name NtfsDumpFileRecord
3260 * @implemented
3261 *
3262 * Provides diagnostic information about a file record. Prints a hex dump
3263 * of the entire record (based on the size reported by FileRecord->ByesInUse),
3264 * then prints a dump of each attribute.
3265 *
3266 * @param Vcb
3267 * Pointer to a DEVICE_EXTENSION describing the volume.
3268 *
3269 * @param FileRecord
3270 * Pointer to the file record to be analyzed.
3271 *
3272 * @remarks
3273 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
3274 * in size, and not just the header.
3275 *
3276 */
3277 VOID
3278 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
3279 PFILE_RECORD_HEADER FileRecord)
3280 {
3281 ULONG i, j;
3282
3283 // dump binary data, 8 bytes at a time
3284 for (i = 0; i < FileRecord->BytesInUse; i += 8)
3285 {
3286 // display current offset, in hex
3287 DbgPrint("\t%03x\t", i);
3288
3289 // display hex value of each of the next 8 bytes
3290 for (j = 0; j < 8; j++)
3291 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
3292 DbgPrint("\n");
3293 }
3294
3295 NtfsDumpFileAttributes(Vcb, FileRecord);
3296 }
3297
3298 NTSTATUS
3299 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
3300 PUNICODE_STRING SearchPattern,
3301 PULONG FirstEntry,
3302 PFILE_RECORD_HEADER *FileRecord,
3303 PULONGLONG MFTIndex,
3304 ULONGLONG CurrentMFTIndex,
3305 BOOLEAN CaseSensitive)
3306 {
3307 NTSTATUS Status;
3308
3309 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n",
3310 Vcb,
3311 SearchPattern,
3312 *FirstEntry,
3313 FileRecord,
3314 MFTIndex,
3315 CurrentMFTIndex,
3316 (CaseSensitive ? "TRUE" : "FALSE"));
3317
3318 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, CaseSensitive, &CurrentMFTIndex);
3319 if (!NT_SUCCESS(Status))
3320 {
3321 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
3322 return Status;
3323 }
3324
3325 *FileRecord = ExAllocateFromNPagedLookasideList(&Vcb->FileRecLookasideList);
3326 if (*FileRecord == NULL)
3327 {
3328 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
3329 return STATUS_INSUFFICIENT_RESOURCES;
3330 }
3331
3332 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
3333 if (!NT_SUCCESS(Status))
3334 {
3335 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
3336 ExFreeToNPagedLookasideList(&Vcb->FileRecLookasideList, *FileRecord);
3337 return Status;
3338 }
3339
3340 *MFTIndex = CurrentMFTIndex;
3341
3342 return STATUS_SUCCESS;
3343 }
3344
3345 /* EOF */