[NTFS] - Fix increasing the mft size, to keep chkdsk happy.
[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 = ExAllocatePoolWithTag(NonPagedPool,
46 sizeof(NTFS_ATTR_CONTEXT),
47 TAG_NTFS);
48 if(!Context)
49 {
50 DPRINT1("Error: Unable to allocate memory for context!\n");
51 return NULL;
52 }
53
54 // Allocate memory for a copy of the attribute
55 Context->pRecord = ExAllocatePoolWithTag(NonPagedPool, AttrRecord->Length, TAG_NTFS);
56 if(!Context->pRecord)
57 {
58 DPRINT1("Error: Unable to allocate memory for attribute record!\n");
59 ExFreePoolWithTag(Context, TAG_NTFS);
60 return NULL;
61 }
62
63 // Copy the attribute
64 RtlCopyMemory(Context->pRecord, AttrRecord, AttrRecord->Length);
65
66 if (AttrRecord->IsNonResident)
67 {
68 LONGLONG DataRunOffset;
69 ULONGLONG DataRunLength;
70 ULONGLONG NextVBN = 0;
71 PUCHAR DataRun = (PUCHAR)((ULONG_PTR)Context->pRecord + Context->pRecord->NonResident.MappingPairsOffset);
72
73 Context->CacheRun = DataRun;
74 Context->CacheRunOffset = 0;
75 Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
76 Context->CacheRunLength = DataRunLength;
77 if (DataRunOffset != -1)
78 {
79 /* Normal run. */
80 Context->CacheRunStartLCN =
81 Context->CacheRunLastLCN = DataRunOffset;
82 }
83 else
84 {
85 /* Sparse run. */
86 Context->CacheRunStartLCN = -1;
87 Context->CacheRunLastLCN = 0;
88 }
89 Context->CacheRunCurrentOffset = 0;
90
91 // Convert the data runs to a map control block
92 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN)))
93 {
94 DPRINT1("Unable to convert data runs to MCB!\n");
95 ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
96 ExFreePoolWithTag(Context, TAG_NTFS);
97 return NULL;
98 }
99 }
100
101 return Context;
102 }
103
104
105 VOID
106 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
107 {
108 if (Context->pRecord->IsNonResident)
109 {
110 FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
111 }
112
113 if(Context->pRecord)
114 ExFreePoolWithTag(Context->pRecord, TAG_NTFS);
115
116 ExFreePoolWithTag(Context, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 ExFreePoolWithTag(BlankFileRecord, TAG_NTFS);
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 DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
791
792 return Status;
793 }
794
795 /**
796 * @name SetResidentAttributeDataLength
797 * @implemented
798 *
799 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
800 *
801 * @param Vcb
802 * Pointer to a DEVICE_EXTENSION describing the target disk.
803 *
804 * @param AttrContext
805 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
806 *
807 * @param AttrOffset
808 * Offset, from the beginning of the record, of the attribute being sized.
809 *
810 * @param FileRecord
811 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
812 * not just the header.
813 *
814 * @param DataSize
815 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
816 *
817 * @return
818 * STATUS_SUCCESS on success;
819 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
820 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
821 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
822 * last attribute listed in the file record.
823 *
824 * @remarks
825 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
826 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
827 * any associated files. Synchronization is the callers responsibility.
828 */
829 NTSTATUS
830 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
831 PNTFS_ATTR_CONTEXT AttrContext,
832 ULONG AttrOffset,
833 PFILE_RECORD_HEADER FileRecord,
834 PLARGE_INTEGER DataSize)
835 {
836 NTSTATUS Status;
837
838 // find the next attribute
839 ULONG NextAttributeOffset = AttrOffset + AttrContext->pRecord->Length;
840 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
841
842 ASSERT(!AttrContext->pRecord->IsNonResident);
843
844 //NtfsDumpFileAttributes(Vcb, FileRecord);
845
846 // Do we need to increase the data length?
847 if (DataSize->QuadPart > AttrContext->pRecord->Resident.ValueLength)
848 {
849 // There's usually padding at the end of a record. Do we need to extend past it?
850 ULONG MaxValueLength = AttrContext->pRecord->Length - AttrContext->pRecord->Resident.ValueOffset;
851 if (MaxValueLength < DataSize->LowPart)
852 {
853 // If this is the last attribute, we could move the end marker to the very end of the file record
854 MaxValueLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
855
856 if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
857 {
858 // convert attribute to non-resident
859 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
860 PNTFS_ATTR_RECORD NewRecord;
861 LARGE_INTEGER AttribDataSize;
862 PVOID AttribData;
863 ULONG NewRecordLength;
864 ULONG EndAttributeOffset;
865 ULONG LengthWritten;
866
867 DPRINT1("Converting attribute to non-resident.\n");
868
869 AttribDataSize.QuadPart = AttrContext->pRecord->Resident.ValueLength;
870
871 // Is there existing data we need to back-up?
872 if (AttribDataSize.QuadPart > 0)
873 {
874 AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
875 if (AttribData == NULL)
876 {
877 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
878 return STATUS_INSUFFICIENT_RESOURCES;
879 }
880
881 // read data to temp buffer
882 Status = ReadAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
883 if (!NT_SUCCESS(Status))
884 {
885 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
886 ExFreePoolWithTag(AttribData, TAG_NTFS);
887 return Status;
888 }
889 }
890
891 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
892
893 // The size of a 0-length, non-resident attribute will be 0x41 + the size of the attribute name, aligned to an 8-byte boundary
894 NewRecordLength = ALIGN_UP_BY(0x41 + (AttrContext->pRecord->NameLength * sizeof(WCHAR)), ATTR_RECORD_ALIGNMENT);
895
896 // Create a new attribute record that will store the 0-length, non-resident attribute
897 NewRecord = ExAllocatePoolWithTag(NonPagedPool, NewRecordLength, TAG_NTFS);
898
899 // Zero out the NonResident structure
900 RtlZeroMemory(NewRecord, NewRecordLength);
901
902 // Copy the data that's common to both non-resident and resident attributes
903 RtlCopyMemory(NewRecord, AttrContext->pRecord, FIELD_OFFSET(NTFS_ATTR_RECORD, Resident.ValueLength));
904
905 // if there's a name
906 if (AttrContext->pRecord->NameLength != 0)
907 {
908 // copy the name
909 // An attribute name will be located at offset 0x18 for a resident attribute, 0x40 for non-resident
910 RtlCopyMemory((PCHAR)((ULONG_PTR)NewRecord + 0x40),
911 (PCHAR)((ULONG_PTR)AttrContext->pRecord + 0x18),
912 AttrContext->pRecord->NameLength * sizeof(WCHAR));
913 }
914
915 // update the mapping pairs offset, which will be 0x40 (size of a non-resident header) + length in bytes of the name
916 NewRecord->NonResident.MappingPairsOffset = 0x40 + (AttrContext->pRecord->NameLength * sizeof(WCHAR));
917
918 // update the end of the file record
919 // calculate position of end markers (1 byte for empty data run)
920 EndAttributeOffset = AttrOffset + NewRecord->NonResident.MappingPairsOffset + 1;
921 EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, ATTR_RECORD_ALIGNMENT);
922
923 // Update the length
924 NewRecord->Length = EndAttributeOffset - AttrOffset;
925
926 ASSERT(NewRecord->Length == NewRecordLength);
927
928 // Copy the new attribute record into the file record
929 RtlCopyMemory(Destination, NewRecord, NewRecord->Length);
930
931 // Update the file record end
932 SetFileRecordEnd(FileRecord,
933 (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset),
934 FILE_RECORD_END);
935
936 // Initialize the MCB, potentially catch an exception
937 _SEH2_TRY
938 {
939 FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
940 }
941 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
942 {
943 DPRINT1("Unable to create LargeMcb!\n");
944 if (AttribDataSize.QuadPart > 0)
945 ExFreePoolWithTag(AttribData, TAG_NTFS);
946 _SEH2_YIELD(return _SEH2_GetExceptionCode());
947 } _SEH2_END;
948
949 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized)
950 NewRecord->IsNonResident = Destination->IsNonResident = 1;
951
952 // Update file record on disk
953 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
954 if (!NT_SUCCESS(Status))
955 {
956 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
957 if (AttribDataSize.QuadPart > 0)
958 ExFreePoolWithTag(AttribData, TAG_NTFS);
959 return Status;
960 }
961
962 // Now we need to free the old copy of the attribute record in the context and replace it with the new one
963 ExFreePoolWithTag(AttrContext->pRecord, TAG_NTFS);
964 AttrContext->pRecord = NewRecord;
965
966 // Now we can treat the attribute as non-resident and enlarge it normally
967 Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize);
968 if (!NT_SUCCESS(Status))
969 {
970 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
971 if (AttribDataSize.QuadPart > 0)
972 ExFreePoolWithTag(AttribData, TAG_NTFS);
973 return Status;
974 }
975
976 // restore the back-up attribute, if we made one
977 if (AttribDataSize.QuadPart > 0)
978 {
979 Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten, FileRecord);
980 if (!NT_SUCCESS(Status))
981 {
982 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
983 // TODO: Reverse migration so no data is lost
984 ExFreePoolWithTag(AttribData, TAG_NTFS);
985 return Status;
986 }
987
988 ExFreePoolWithTag(AttribData, TAG_NTFS);
989 }
990 }
991 }
992 }
993
994 // set the new length of the resident attribute (if we didn't migrate it)
995 if (!AttrContext->pRecord->IsNonResident)
996 return InternalSetResidentAttributeLength(Vcb, AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
997
998 return STATUS_SUCCESS;
999 }
1000
1001 ULONG
1002 ReadAttribute(PDEVICE_EXTENSION Vcb,
1003 PNTFS_ATTR_CONTEXT Context,
1004 ULONGLONG Offset,
1005 PCHAR Buffer,
1006 ULONG Length)
1007 {
1008 ULONGLONG LastLCN;
1009 PUCHAR DataRun;
1010 LONGLONG DataRunOffset;
1011 ULONGLONG DataRunLength;
1012 LONGLONG DataRunStartLCN;
1013 ULONGLONG CurrentOffset;
1014 ULONG ReadLength;
1015 ULONG AlreadyRead;
1016 NTSTATUS Status;
1017
1018 //TEMPTEMP
1019 PUCHAR TempBuffer;
1020
1021 if (!Context->pRecord->IsNonResident)
1022 {
1023 // We need to truncate Offset to a ULONG for pointer arithmetic
1024 // The check below should ensure that Offset is well within the range of 32 bits
1025 ULONG LittleOffset = (ULONG)Offset;
1026
1027 // Ensure that offset isn't beyond the end of the attribute
1028 if (Offset > Context->pRecord->Resident.ValueLength)
1029 return 0;
1030 if (Offset + Length > Context->pRecord->Resident.ValueLength)
1031 Length = (ULONG)(Context->pRecord->Resident.ValueLength - Offset);
1032
1033 RtlCopyMemory(Buffer, (PVOID)((ULONG_PTR)Context->pRecord + Context->pRecord->Resident.ValueOffset + LittleOffset), Length);
1034 return Length;
1035 }
1036
1037 /*
1038 * Non-resident attribute
1039 */
1040
1041 /*
1042 * I. Find the corresponding start data run.
1043 */
1044
1045 AlreadyRead = 0;
1046
1047 // FIXME: Cache seems to be non-working. Disable it for now
1048 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1049 if (0)
1050 {
1051 DataRun = Context->CacheRun;
1052 LastLCN = Context->CacheRunLastLCN;
1053 DataRunStartLCN = Context->CacheRunStartLCN;
1054 DataRunLength = Context->CacheRunLength;
1055 CurrentOffset = Context->CacheRunCurrentOffset;
1056 }
1057 else
1058 {
1059 //TEMPTEMP
1060 ULONG UsedBufferSize;
1061 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1062
1063 LastLCN = 0;
1064 CurrentOffset = 0;
1065
1066 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1067 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
1068 TempBuffer,
1069 Vcb->NtfsInfo.BytesPerFileRecord,
1070 &UsedBufferSize);
1071
1072 DataRun = TempBuffer;
1073
1074 while (1)
1075 {
1076 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1077 if (DataRunOffset != -1)
1078 {
1079 /* Normal data run. */
1080 DataRunStartLCN = LastLCN + DataRunOffset;
1081 LastLCN = DataRunStartLCN;
1082 }
1083 else
1084 {
1085 /* Sparse data run. */
1086 DataRunStartLCN = -1;
1087 }
1088
1089 if (Offset >= CurrentOffset &&
1090 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
1091 {
1092 break;
1093 }
1094
1095 if (*DataRun == 0)
1096 {
1097 return AlreadyRead;
1098 }
1099
1100 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1101 }
1102 }
1103
1104 /*
1105 * II. Go through the run list and read the data
1106 */
1107
1108 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
1109 if (DataRunStartLCN == -1)
1110 {
1111 RtlZeroMemory(Buffer, ReadLength);
1112 Status = STATUS_SUCCESS;
1113 }
1114 else
1115 {
1116 Status = NtfsReadDisk(Vcb->StorageDevice,
1117 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
1118 ReadLength,
1119 Vcb->NtfsInfo.BytesPerSector,
1120 (PVOID)Buffer,
1121 FALSE);
1122 }
1123 if (NT_SUCCESS(Status))
1124 {
1125 Length -= ReadLength;
1126 Buffer += ReadLength;
1127 AlreadyRead += ReadLength;
1128
1129 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
1130 {
1131 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1132 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1133 if (DataRunOffset != (ULONGLONG)-1)
1134 {
1135 DataRunStartLCN = LastLCN + DataRunOffset;
1136 LastLCN = DataRunStartLCN;
1137 }
1138 else
1139 DataRunStartLCN = -1;
1140 }
1141
1142 while (Length > 0)
1143 {
1144 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
1145 if (DataRunStartLCN == -1)
1146 RtlZeroMemory(Buffer, ReadLength);
1147 else
1148 {
1149 Status = NtfsReadDisk(Vcb->StorageDevice,
1150 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
1151 ReadLength,
1152 Vcb->NtfsInfo.BytesPerSector,
1153 (PVOID)Buffer,
1154 FALSE);
1155 if (!NT_SUCCESS(Status))
1156 break;
1157 }
1158
1159 Length -= ReadLength;
1160 Buffer += ReadLength;
1161 AlreadyRead += ReadLength;
1162
1163 /* We finished this request, but there still data in this data run. */
1164 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1165 break;
1166
1167 /*
1168 * Go to next run in the list.
1169 */
1170
1171 if (*DataRun == 0)
1172 break;
1173 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1174 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1175 if (DataRunOffset != -1)
1176 {
1177 /* Normal data run. */
1178 DataRunStartLCN = LastLCN + DataRunOffset;
1179 LastLCN = DataRunStartLCN;
1180 }
1181 else
1182 {
1183 /* Sparse data run. */
1184 DataRunStartLCN = -1;
1185 }
1186 } /* while */
1187
1188 } /* if Disk */
1189
1190 // TEMPTEMP
1191 if (Context->pRecord->IsNonResident)
1192 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1193
1194 Context->CacheRun = DataRun;
1195 Context->CacheRunOffset = Offset + AlreadyRead;
1196 Context->CacheRunStartLCN = DataRunStartLCN;
1197 Context->CacheRunLength = DataRunLength;
1198 Context->CacheRunLastLCN = LastLCN;
1199 Context->CacheRunCurrentOffset = CurrentOffset;
1200
1201 return AlreadyRead;
1202 }
1203
1204
1205 /**
1206 * @name WriteAttribute
1207 * @implemented
1208 *
1209 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1210 * and it still needs more documentation / cleaning up.
1211 *
1212 * @param Vcb
1213 * Volume Control Block indicating which volume to write the attribute to
1214 *
1215 * @param Context
1216 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1217 *
1218 * @param Offset
1219 * Offset, in bytes, from the beginning of the attribute indicating where to start
1220 * writing data
1221 *
1222 * @param Buffer
1223 * The data that's being written to the device
1224 *
1225 * @param Length
1226 * How much data will be written, in bytes
1227 *
1228 * @param RealLengthWritten
1229 * Pointer to a ULONG which will receive how much data was written, in bytes
1230 *
1231 * @param FileRecord
1232 * Optional pointer to a FILE_RECORD_HEADER that contains a copy of the file record
1233 * being written to. Can be NULL, in which case the file record will be read from disk.
1234 * If not-null, WriteAttribute() will skip reading from disk, and FileRecord
1235 * will be updated with the newly-written attribute before the function returns.
1236 *
1237 * @return
1238 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1239 * writing to a sparse file.
1240 *
1241 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1242 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1243 *
1244 */
1245
1246 NTSTATUS
1247 WriteAttribute(PDEVICE_EXTENSION Vcb,
1248 PNTFS_ATTR_CONTEXT Context,
1249 ULONGLONG Offset,
1250 const PUCHAR Buffer,
1251 ULONG Length,
1252 PULONG RealLengthWritten,
1253 PFILE_RECORD_HEADER FileRecord)
1254 {
1255 ULONGLONG LastLCN;
1256 PUCHAR DataRun;
1257 LONGLONG DataRunOffset;
1258 ULONGLONG DataRunLength;
1259 LONGLONG DataRunStartLCN;
1260 ULONGLONG CurrentOffset;
1261 ULONG WriteLength;
1262 NTSTATUS Status;
1263 PUCHAR SourceBuffer = Buffer;
1264 LONGLONG StartingOffset;
1265 BOOLEAN FileRecordAllocated = FALSE;
1266
1267 //TEMPTEMP
1268 PUCHAR TempBuffer;
1269
1270
1271 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten, FileRecord);
1272
1273 *RealLengthWritten = 0;
1274
1275 // is this a resident attribute?
1276 if (!Context->pRecord->IsNonResident)
1277 {
1278 ULONG AttributeOffset;
1279 PNTFS_ATTR_CONTEXT FoundContext;
1280 PNTFS_ATTR_RECORD Destination;
1281
1282 // Ensure requested data is within the bounds of the attribute
1283 ASSERT(Offset + Length <= Context->pRecord->Resident.ValueLength);
1284
1285 if (Offset + Length > Context->pRecord->Resident.ValueLength)
1286 {
1287 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1288 return STATUS_INVALID_PARAMETER;
1289 }
1290
1291 // Do we need to read the file record?
1292 if (FileRecord == NULL)
1293 {
1294 FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1295 if (!FileRecord)
1296 {
1297 DPRINT1("Error: Couldn't allocate file record!\n");
1298 return STATUS_NO_MEMORY;
1299 }
1300
1301 FileRecordAllocated = TRUE;
1302
1303 // read the file record
1304 ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1305 }
1306
1307 // find where to write the attribute data to
1308 Status = FindAttribute(Vcb, FileRecord,
1309 Context->pRecord->Type,
1310 (PCWSTR)((ULONG_PTR)Context->pRecord + Context->pRecord->NameOffset),
1311 Context->pRecord->NameLength,
1312 &FoundContext,
1313 &AttributeOffset);
1314
1315 if (!NT_SUCCESS(Status))
1316 {
1317 DPRINT1("ERROR: Couldn't find matching attribute!\n");
1318 if(FileRecordAllocated)
1319 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1320 return Status;
1321 }
1322
1323 Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttributeOffset);
1324
1325 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->pRecord->Resident.ValueLength);
1326
1327 // Will we be writing past the end of the allocated file record?
1328 if (Offset + Length + AttributeOffset + Context->pRecord->Resident.ValueOffset > Vcb->NtfsInfo.BytesPerFileRecord)
1329 {
1330 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1331 ReleaseAttributeContext(FoundContext);
1332 if (FileRecordAllocated)
1333 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1334 return STATUS_INVALID_PARAMETER;
1335 }
1336
1337 // copy the data being written into the file record. We cast Offset to ULONG, which is safe because it's range has been verified.
1338 RtlCopyMemory((PCHAR)((ULONG_PTR)Destination + Context->pRecord->Resident.ValueOffset + (ULONG)Offset), Buffer, Length);
1339
1340 Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1341
1342 // Update the context's copy of the resident attribute
1343 ASSERT(Context->pRecord->Length == Destination->Length);
1344 RtlCopyMemory((PVOID)Context->pRecord, Destination, Context->pRecord->Length);
1345
1346 ReleaseAttributeContext(FoundContext);
1347 if (FileRecordAllocated)
1348 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1349
1350 if (NT_SUCCESS(Status))
1351 *RealLengthWritten = Length;
1352
1353 return Status;
1354 }
1355
1356 // This is a non-resident attribute.
1357
1358 // I. Find the corresponding start data run.
1359
1360 // FIXME: Cache seems to be non-working. Disable it for now
1361 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1362 /*if (0)
1363 {
1364 DataRun = Context->CacheRun;
1365 LastLCN = Context->CacheRunLastLCN;
1366 DataRunStartLCN = Context->CacheRunStartLCN;
1367 DataRunLength = Context->CacheRunLength;
1368 CurrentOffset = Context->CacheRunCurrentOffset;
1369 }
1370 else*/
1371 {
1372 ULONG UsedBufferSize;
1373 LastLCN = 0;
1374 CurrentOffset = 0;
1375
1376 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1377 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1378
1379 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
1380 TempBuffer,
1381 Vcb->NtfsInfo.BytesPerFileRecord,
1382 &UsedBufferSize);
1383
1384 DataRun = TempBuffer;
1385
1386 while (1)
1387 {
1388 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1389 if (DataRunOffset != -1)
1390 {
1391 // Normal data run.
1392 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1393 DataRunStartLCN = LastLCN + DataRunOffset;
1394 LastLCN = DataRunStartLCN;
1395 }
1396 else
1397 {
1398 // Sparse data run. We can't support writing to sparse files yet
1399 // (it may require increasing the allocation size).
1400 DataRunStartLCN = -1;
1401 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1402 return STATUS_NOT_IMPLEMENTED;
1403 }
1404
1405 // Have we reached the data run we're trying to write to?
1406 if (Offset >= CurrentOffset &&
1407 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
1408 {
1409 break;
1410 }
1411
1412 if (*DataRun == 0)
1413 {
1414 // We reached the last assigned cluster
1415 // TODO: assign new clusters to the end of the file.
1416 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1417 // [We can reach here by creating a new file record when the MFT isn't large enough]
1418 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1419 return STATUS_END_OF_FILE;
1420 }
1421
1422 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1423 }
1424 }
1425
1426 // II. Go through the run list and write the data
1427
1428 /* REVIEWME -- As adapted from NtfsReadAttribute():
1429 We seem to be making a special case for the first applicable data run, but I'm not sure why.
1430 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1431
1432 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
1433
1434 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
1435
1436 // Write the data to the disk
1437 Status = NtfsWriteDisk(Vcb->StorageDevice,
1438 StartingOffset,
1439 WriteLength,
1440 Vcb->NtfsInfo.BytesPerSector,
1441 (PVOID)SourceBuffer);
1442
1443 // Did the write fail?
1444 if (!NT_SUCCESS(Status))
1445 {
1446 Context->CacheRun = DataRun;
1447 Context->CacheRunOffset = Offset;
1448 Context->CacheRunStartLCN = DataRunStartLCN;
1449 Context->CacheRunLength = DataRunLength;
1450 Context->CacheRunLastLCN = LastLCN;
1451 Context->CacheRunCurrentOffset = CurrentOffset;
1452
1453 return Status;
1454 }
1455
1456 Length -= WriteLength;
1457 SourceBuffer += WriteLength;
1458 *RealLengthWritten += WriteLength;
1459
1460 // Did we write to the end of the data run?
1461 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
1462 {
1463 // Advance to the next data run
1464 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1465 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1466
1467 if (DataRunOffset != (ULONGLONG)-1)
1468 {
1469 DataRunStartLCN = LastLCN + DataRunOffset;
1470 LastLCN = DataRunStartLCN;
1471 }
1472 else
1473 DataRunStartLCN = -1;
1474 }
1475
1476 // Do we have more data to write?
1477 while (Length > 0)
1478 {
1479 // Make sure we don't write past the end of the current data run
1480 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
1481
1482 // Are we dealing with a sparse data run?
1483 if (DataRunStartLCN == -1)
1484 {
1485 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1486 return STATUS_NOT_IMPLEMENTED;
1487 }
1488 else
1489 {
1490 // write the data to the disk
1491 Status = NtfsWriteDisk(Vcb->StorageDevice,
1492 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
1493 WriteLength,
1494 Vcb->NtfsInfo.BytesPerSector,
1495 (PVOID)SourceBuffer);
1496 if (!NT_SUCCESS(Status))
1497 break;
1498 }
1499
1500 Length -= WriteLength;
1501 SourceBuffer += WriteLength;
1502 *RealLengthWritten += WriteLength;
1503
1504 // We finished this request, but there's still data in this data run.
1505 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1506 break;
1507
1508 // Go to next run in the list.
1509
1510 if (*DataRun == 0)
1511 {
1512 // that was the last run
1513 if (Length > 0)
1514 {
1515 // Failed sanity check.
1516 DPRINT1("Encountered EOF before expected!\n");
1517 return STATUS_END_OF_FILE;
1518 }
1519
1520 break;
1521 }
1522
1523 // Advance to the next data run
1524 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1525 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1526 if (DataRunOffset != -1)
1527 {
1528 // Normal data run.
1529 DataRunStartLCN = LastLCN + DataRunOffset;
1530 LastLCN = DataRunStartLCN;
1531 }
1532 else
1533 {
1534 // Sparse data run.
1535 DataRunStartLCN = -1;
1536 }
1537 } // end while (Length > 0) [more data to write]
1538
1539 // TEMPTEMP
1540 if (Context->pRecord->IsNonResident)
1541 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1542
1543 Context->CacheRun = DataRun;
1544 Context->CacheRunOffset = Offset + *RealLengthWritten;
1545 Context->CacheRunStartLCN = DataRunStartLCN;
1546 Context->CacheRunLength = DataRunLength;
1547 Context->CacheRunLastLCN = LastLCN;
1548 Context->CacheRunCurrentOffset = CurrentOffset;
1549
1550 return Status;
1551 }
1552
1553 NTSTATUS
1554 ReadFileRecord(PDEVICE_EXTENSION Vcb,
1555 ULONGLONG index,
1556 PFILE_RECORD_HEADER file)
1557 {
1558 ULONGLONG BytesRead;
1559
1560 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
1561
1562 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
1563 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
1564 {
1565 DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
1566 return STATUS_PARTIAL_COPY;
1567 }
1568
1569 /* Apply update sequence array fixups. */
1570 DPRINT("Sequence number: %u\n", file->SequenceNumber);
1571 return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
1572 }
1573
1574
1575 /**
1576 * Searches a file's parent directory (given the parent's index in the mft)
1577 * for the given file. Upon finding an index entry for that file, updates
1578 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1579 *
1580 * (Most of this code was copied from NtfsFindMftRecord)
1581 */
1582 NTSTATUS
1583 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
1584 ULONGLONG ParentMFTIndex,
1585 PUNICODE_STRING FileName,
1586 BOOLEAN DirSearch,
1587 ULONGLONG NewDataSize,
1588 ULONGLONG NewAllocationSize,
1589 BOOLEAN CaseSensitive)
1590 {
1591 PFILE_RECORD_HEADER MftRecord;
1592 PNTFS_ATTR_CONTEXT IndexRootCtx;
1593 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1594 PCHAR IndexRecord;
1595 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1596 NTSTATUS Status;
1597 ULONG CurrentEntry = 0;
1598
1599 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n",
1600 Vcb,
1601 ParentMFTIndex,
1602 FileName,
1603 DirSearch ? "TRUE" : "FALSE",
1604 NewDataSize,
1605 NewAllocationSize,
1606 CaseSensitive ? "TRUE" : "FALSE");
1607
1608 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1609 Vcb->NtfsInfo.BytesPerFileRecord,
1610 TAG_NTFS);
1611 if (MftRecord == NULL)
1612 {
1613 return STATUS_INSUFFICIENT_RESOURCES;
1614 }
1615
1616 Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
1617 if (!NT_SUCCESS(Status))
1618 {
1619 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1620 return Status;
1621 }
1622
1623 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1624 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1625 if (!NT_SUCCESS(Status))
1626 {
1627 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1628 return Status;
1629 }
1630
1631 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1632 if (IndexRecord == NULL)
1633 {
1634 ReleaseAttributeContext(IndexRootCtx);
1635 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1636 return STATUS_INSUFFICIENT_RESOURCES;
1637 }
1638
1639 Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(IndexRootCtx->pRecord));
1640 if (!NT_SUCCESS(Status))
1641 {
1642 DPRINT1("ERROR: Failed to read Index Root!\n");
1643 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1644 ReleaseAttributeContext(IndexRootCtx);
1645 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1646 }
1647
1648 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1649 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1650 // Index root is always resident.
1651 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1652
1653 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1654
1655 Status = UpdateIndexEntryFileNameSize(Vcb,
1656 MftRecord,
1657 IndexRecord,
1658 IndexRoot->SizeOfEntry,
1659 IndexEntry,
1660 IndexEntryEnd,
1661 FileName,
1662 &CurrentEntry,
1663 &CurrentEntry,
1664 DirSearch,
1665 NewDataSize,
1666 NewAllocationSize,
1667 CaseSensitive);
1668
1669 if (Status == STATUS_PENDING)
1670 {
1671 // we need to write the index root attribute back to disk
1672 ULONG LengthWritten;
1673 Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(IndexRootCtx->pRecord), &LengthWritten, MftRecord);
1674 if (!NT_SUCCESS(Status))
1675 {
1676 DPRINT1("ERROR: Couldn't update Index Root!\n");
1677 }
1678
1679 }
1680
1681 ReleaseAttributeContext(IndexRootCtx);
1682 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1683 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1684
1685 return Status;
1686 }
1687
1688 /**
1689 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1690 * proper index entry.
1691 * (Heavily based on BrowseIndexEntries)
1692 */
1693 NTSTATUS
1694 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
1695 PFILE_RECORD_HEADER MftRecord,
1696 PCHAR IndexRecord,
1697 ULONG IndexBlockSize,
1698 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1699 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1700 PUNICODE_STRING FileName,
1701 PULONG StartEntry,
1702 PULONG CurrentEntry,
1703 BOOLEAN DirSearch,
1704 ULONGLONG NewDataSize,
1705 ULONGLONG NewAllocatedSize,
1706 BOOLEAN CaseSensitive)
1707 {
1708 NTSTATUS Status;
1709 ULONG RecordOffset;
1710 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1711 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1712 ULONGLONG IndexAllocationSize;
1713 PINDEX_BUFFER IndexBuffer;
1714
1715 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n",
1716 Vcb,
1717 MftRecord,
1718 IndexRecord,
1719 IndexBlockSize,
1720 FirstEntry,
1721 LastEntry,
1722 FileName,
1723 *StartEntry,
1724 *CurrentEntry,
1725 DirSearch ? "TRUE" : "FALSE",
1726 NewDataSize,
1727 NewAllocatedSize,
1728 CaseSensitive ? "TRUE" : "FALSE");
1729
1730 // find the index entry responsible for the file we're trying to update
1731 IndexEntry = FirstEntry;
1732 while (IndexEntry < LastEntry &&
1733 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1734 {
1735 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > NTFS_FILE_FIRST_USER_FILE &&
1736 *CurrentEntry >= *StartEntry &&
1737 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1738 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
1739 {
1740 *StartEntry = *CurrentEntry;
1741 IndexEntry->FileName.DataSize = NewDataSize;
1742 IndexEntry->FileName.AllocatedSize = NewAllocatedSize;
1743 // indicate that the caller will still need to write the structure to the disk
1744 return STATUS_PENDING;
1745 }
1746
1747 (*CurrentEntry) += 1;
1748 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1749 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1750 }
1751
1752 /* If we're already browsing a subnode */
1753 if (IndexRecord == NULL)
1754 {
1755 return STATUS_OBJECT_PATH_NOT_FOUND;
1756 }
1757
1758 /* If there's no subnode */
1759 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1760 {
1761 return STATUS_OBJECT_PATH_NOT_FOUND;
1762 }
1763
1764 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1765 if (!NT_SUCCESS(Status))
1766 {
1767 DPRINT("Corrupted filesystem!\n");
1768 return Status;
1769 }
1770
1771 IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord);
1772 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1773 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1774 {
1775 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1776 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1777 if (!NT_SUCCESS(Status))
1778 {
1779 break;
1780 }
1781
1782 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1783 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1784 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1785 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1786 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1787 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1788
1789 Status = UpdateIndexEntryFileNameSize(NULL,
1790 NULL,
1791 NULL,
1792 0,
1793 FirstEntry,
1794 LastEntry,
1795 FileName,
1796 StartEntry,
1797 CurrentEntry,
1798 DirSearch,
1799 NewDataSize,
1800 NewAllocatedSize,
1801 CaseSensitive);
1802 if (Status == STATUS_PENDING)
1803 {
1804 // write the index record back to disk
1805 ULONG Written;
1806
1807 // first we need to update the fixup values for the index block
1808 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1809 if (!NT_SUCCESS(Status))
1810 {
1811 DPRINT1("Error: Failed to update fixup sequence array!\n");
1812 break;
1813 }
1814
1815 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written, MftRecord);
1816 if (!NT_SUCCESS(Status))
1817 {
1818 DPRINT1("ERROR Performing write!\n");
1819 break;
1820 }
1821
1822 Status = STATUS_SUCCESS;
1823 break;
1824 }
1825 if (NT_SUCCESS(Status))
1826 {
1827 break;
1828 }
1829 }
1830
1831 ReleaseAttributeContext(IndexAllocationCtx);
1832 return Status;
1833 }
1834
1835 /**
1836 * @name UpdateFileRecord
1837 * @implemented
1838 *
1839 * Writes a file record to the master file table, at a given index.
1840 *
1841 * @param Vcb
1842 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1843 *
1844 * @param MftIndex
1845 * Target index in the master file table to store the file record.
1846 *
1847 * @param FileRecord
1848 * Pointer to the complete file record which will be written to the master file table.
1849 *
1850 * @return
1851 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1852 *
1853 */
1854 NTSTATUS
1855 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
1856 ULONGLONG MftIndex,
1857 PFILE_RECORD_HEADER FileRecord)
1858 {
1859 ULONG BytesWritten;
1860 NTSTATUS Status = STATUS_SUCCESS;
1861
1862 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord);
1863
1864 // Add the fixup array to prepare the data for writing to disk
1865 AddFixupArray(Vcb, &FileRecord->Ntfs);
1866
1867 // write the file record to the master file table
1868 Status = WriteAttribute(Vcb,
1869 Vcb->MFTContext,
1870 MftIndex * Vcb->NtfsInfo.BytesPerFileRecord,
1871 (const PUCHAR)FileRecord,
1872 Vcb->NtfsInfo.BytesPerFileRecord,
1873 &BytesWritten,
1874 FileRecord);
1875
1876 if (!NT_SUCCESS(Status))
1877 {
1878 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
1879 }
1880
1881 // remove the fixup array (so the file record pointer can still be used)
1882 FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs);
1883
1884 return Status;
1885 }
1886
1887
1888 NTSTATUS
1889 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
1890 PNTFS_RECORD_HEADER Record)
1891 {
1892 USHORT *USA;
1893 USHORT USANumber;
1894 USHORT USACount;
1895 USHORT *Block;
1896
1897 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
1898 USANumber = *(USA++);
1899 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
1900 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
1901
1902 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
1903
1904 while (USACount)
1905 {
1906 if (*Block != USANumber)
1907 {
1908 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
1909 return STATUS_UNSUCCESSFUL;
1910 }
1911 *Block = *(USA++);
1912 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
1913 USACount--;
1914 }
1915
1916 return STATUS_SUCCESS;
1917 }
1918
1919 /**
1920 * @name AddNewMftEntry
1921 * @implemented
1922 *
1923 * Adds a file record to the master file table of a given device.
1924 *
1925 * @param FileRecord
1926 * Pointer to a complete file record which will be saved to disk.
1927 *
1928 * @param DeviceExt
1929 * Pointer to the DEVICE_EXTENSION of the target drive.
1930 *
1931 * @param DestinationIndex
1932 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1933 *
1934 * @param CanWait
1935 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
1936 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
1937 *
1938 * @return
1939 * STATUS_SUCCESS on success.
1940 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1941 * to read the attribute.
1942 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1943 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
1944 */
1945 NTSTATUS
1946 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
1947 PDEVICE_EXTENSION DeviceExt,
1948 PULONGLONG DestinationIndex,
1949 BOOLEAN CanWait)
1950 {
1951 NTSTATUS Status = STATUS_SUCCESS;
1952 ULONGLONG MftIndex;
1953 RTL_BITMAP Bitmap;
1954 ULONGLONG BitmapDataSize;
1955 ULONGLONG AttrBytesRead;
1956 PUCHAR BitmapData;
1957 PUCHAR BitmapBuffer;
1958 ULONG LengthWritten;
1959 PNTFS_ATTR_CONTEXT BitmapContext;
1960 LARGE_INTEGER BitmapBits;
1961 UCHAR SystemReservedBits;
1962
1963 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
1964
1965 // First, we have to read the mft's $Bitmap attribute
1966
1967 // Find the attribute
1968 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
1969 if (!NT_SUCCESS(Status))
1970 {
1971 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1972 return Status;
1973 }
1974
1975 // Get size of bitmap
1976 BitmapDataSize = AttributeDataLength(BitmapContext->pRecord);
1977
1978 // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple
1979 // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer
1980 BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS);
1981 if (!BitmapBuffer)
1982 {
1983 ReleaseAttributeContext(BitmapContext);
1984 return STATUS_INSUFFICIENT_RESOURCES;
1985 }
1986
1987 // Get a ULONG-aligned pointer for the bitmap itself
1988 BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG));
1989
1990 // read $Bitmap attribute
1991 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize);
1992
1993 if (AttrBytesRead != BitmapDataSize)
1994 {
1995 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1996 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
1997 ReleaseAttributeContext(BitmapContext);
1998 return STATUS_OBJECT_NAME_NOT_FOUND;
1999 }
2000
2001 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records
2002 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk).
2003 SystemReservedBits = BitmapData[2];
2004 BitmapData[2] = 0xff;
2005
2006 // Calculate bit count
2007 BitmapBits.QuadPart = AttributeDataLength(DeviceExt->MFTContext->pRecord) /
2008 DeviceExt->NtfsInfo.BytesPerFileRecord;
2009 if (BitmapBits.HighPart != 0)
2010 {
2011 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
2012 NtfsGlobalData->EnableWriteSupport = FALSE;
2013 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2014 ReleaseAttributeContext(BitmapContext);
2015 return STATUS_NOT_IMPLEMENTED;
2016 }
2017
2018 // convert buffer into bitmap
2019 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart);
2020
2021 // set next available bit, preferrably after 23rd bit
2022 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
2023 if ((LONG)MftIndex == -1)
2024 {
2025 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
2026
2027 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2028 ReleaseAttributeContext(BitmapContext);
2029
2030 // Couldn't find a free record in the MFT, add some blank records and try again
2031 Status = IncreaseMftSize(DeviceExt, CanWait);
2032 if (!NT_SUCCESS(Status))
2033 {
2034 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
2035 return Status;
2036 }
2037
2038 return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait);
2039 }
2040
2041 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
2042
2043 // update file record with index
2044 FileRecord->MFTRecordNumber = MftIndex;
2045
2046 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
2047
2048 // Restore the system reserved bits
2049 BitmapData[2] = SystemReservedBits;
2050
2051 // write the bitmap back to the MFT's $Bitmap attribute
2052 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord);
2053 if (!NT_SUCCESS(Status))
2054 {
2055 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
2056 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2057 ReleaseAttributeContext(BitmapContext);
2058 return Status;
2059 }
2060
2061 // update the file record (write it to disk)
2062 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
2063
2064 if (!NT_SUCCESS(Status))
2065 {
2066 DPRINT1("ERROR: Unable to write file record!\n");
2067 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2068 ReleaseAttributeContext(BitmapContext);
2069 return Status;
2070 }
2071
2072 *DestinationIndex = MftIndex;
2073
2074 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2075 ReleaseAttributeContext(BitmapContext);
2076
2077 return Status;
2078 }
2079
2080 /**
2081 * @name NtfsAddFilenameToDirectory
2082 * @implemented
2083 *
2084 * Adds a $FILE_NAME attribute to a given directory index.
2085 *
2086 * @param DeviceExt
2087 * Points to the target disk's DEVICE_EXTENSION.
2088 *
2089 * @param DirectoryMftIndex
2090 * Mft index of the parent directory which will receive the file.
2091 *
2092 * @param FileReferenceNumber
2093 * File reference of the file to be added to the directory. This is a combination of the
2094 * Mft index and sequence number.
2095 *
2096 * @param FilenameAttribute
2097 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
2098 *
2099 * @param CaseSensitive
2100 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
2101 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
2102 *
2103 * @return
2104 * STATUS_SUCCESS on success.
2105 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
2106 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
2107 *
2108 * @remarks
2109 * WIP - Can only support a few files in a directory.
2110 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
2111 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
2112 * get both attributes added to its parent directory.
2113 */
2114 NTSTATUS
2115 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
2116 ULONGLONG DirectoryMftIndex,
2117 ULONGLONG FileReferenceNumber,
2118 PFILENAME_ATTRIBUTE FilenameAttribute,
2119 BOOLEAN CaseSensitive)
2120 {
2121 NTSTATUS Status = STATUS_SUCCESS;
2122 PFILE_RECORD_HEADER ParentFileRecord;
2123 PNTFS_ATTR_CONTEXT IndexRootContext;
2124 PINDEX_ROOT_ATTRIBUTE I30IndexRoot;
2125 ULONG IndexRootOffset;
2126 ULONGLONG I30IndexRootLength;
2127 ULONG LengthWritten;
2128 PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
2129 ULONG AttributeLength;
2130 PNTFS_ATTR_RECORD NextAttribute;
2131 PB_TREE NewTree;
2132 ULONG BtreeIndexLength;
2133 ULONG MaxIndexRootSize;
2134
2135 // Allocate memory for the parent directory
2136 ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool,
2137 DeviceExt->NtfsInfo.BytesPerFileRecord,
2138 TAG_NTFS);
2139 if (!ParentFileRecord)
2140 {
2141 DPRINT1("ERROR: Couldn't allocate memory for file record!\n");
2142 return STATUS_INSUFFICIENT_RESOURCES;
2143 }
2144
2145 // Open the parent directory
2146 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2147 if (!NT_SUCCESS(Status))
2148 {
2149 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2150 DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n",
2151 DirectoryMftIndex);
2152 return Status;
2153 }
2154
2155 DPRINT1("Dumping old parent file record:\n");
2156 NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
2157
2158 // Find the index root attribute for the directory
2159 Status = FindAttribute(DeviceExt,
2160 ParentFileRecord,
2161 AttributeIndexRoot,
2162 L"$I30",
2163 4,
2164 &IndexRootContext,
2165 &IndexRootOffset);
2166 if (!NT_SUCCESS(Status))
2167 {
2168 DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n",
2169 DirectoryMftIndex);
2170 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2171 return Status;
2172 }
2173
2174 // Find the maximum index size given what the file record can hold
2175 // First, find the max index size assuming index root is the last attribute
2176 MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
2177 - IndexRootOffset // Subtract the length of everything that comes before index root
2178 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
2179 - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) // Subtract the length of the index header for index root
2180 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding
2181
2182 // Are there attributes after this one?
2183 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
2184 if (NextAttribute->Type != AttributeEnd)
2185 {
2186 // Find the length of all attributes after this one, not counting the end marker
2187 ULONG LengthOfAttributes = 0;
2188 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
2189 while (CurrentAttribute->Type != AttributeEnd)
2190 {
2191 LengthOfAttributes += CurrentAttribute->Length;
2192 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
2193 }
2194
2195 // Leave room for the existing attributes
2196 MaxIndexRootSize -= LengthOfAttributes;
2197 }
2198
2199 // Allocate memory for the index root data
2200 I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord);
2201 I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS);
2202 if (!I30IndexRoot)
2203 {
2204 DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n");
2205 ReleaseAttributeContext(IndexRootContext);
2206 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2207 return STATUS_INSUFFICIENT_RESOURCES;
2208 }
2209
2210 // Read the Index Root
2211 Status = ReadAttribute(DeviceExt, IndexRootContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength);
2212 if (!NT_SUCCESS(Status))
2213 {
2214 DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex);
2215 ReleaseAttributeContext(IndexRootContext);
2216 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2217 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2218 return Status;
2219 }
2220
2221 // Convert the index to a B*Tree
2222 Status = CreateBTreeFromIndex(DeviceExt,
2223 ParentFileRecord,
2224 IndexRootContext,
2225 I30IndexRoot,
2226 &NewTree);
2227 if (!NT_SUCCESS(Status))
2228 {
2229 DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
2230 ReleaseAttributeContext(IndexRootContext);
2231 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2232 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2233 return Status;
2234 }
2235
2236 DumpBTree(NewTree);
2237
2238 // Insert the key for the file we're adding
2239 Status = NtfsInsertKey(NewTree, FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive, MaxIndexRootSize);
2240 if (!NT_SUCCESS(Status))
2241 {
2242 DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
2243 DestroyBTree(NewTree);
2244 ReleaseAttributeContext(IndexRootContext);
2245 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2246 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2247 return Status;
2248 }
2249
2250 DumpBTree(NewTree);
2251
2252 // Convert B*Tree back to Index, starting with the index allocation
2253 Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
2254 if (!NT_SUCCESS(Status))
2255 {
2256 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2257 DestroyBTree(NewTree);
2258 ReleaseAttributeContext(IndexRootContext);
2259 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2260 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2261 return Status;
2262 }
2263
2264 // Create the Index Root from the B*Tree
2265 Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength);
2266 if (!NT_SUCCESS(Status))
2267 {
2268 DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
2269 DestroyBTree(NewTree);
2270 ReleaseAttributeContext(IndexRootContext);
2271 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2272 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2273 return Status;
2274 }
2275
2276 // We're done with the B-Tree now
2277 DestroyBTree(NewTree);
2278
2279 // Write back the new index root attribute to the parent directory file record
2280
2281 // First, we need to resize the attribute.
2282 // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
2283 // We can't set the size as we normally would, because if we extend past the file record,
2284 // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with
2285 // $ATTRIBUTE_LIST's.
2286 AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
2287
2288 if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength)
2289 {
2290 // Update the length of the attribute in the file record of the parent directory
2291 Status = InternalSetResidentAttributeLength(DeviceExt,
2292 IndexRootContext,
2293 ParentFileRecord,
2294 IndexRootOffset,
2295 AttributeLength);
2296 if (!NT_SUCCESS(Status))
2297 {
2298 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2299 ReleaseAttributeContext(IndexRootContext);
2300 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2301 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2302 DPRINT1("ERROR: Unable to set resident attribute length!\n");
2303 return Status;
2304 }
2305
2306 }
2307
2308 NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord);
2309
2310 Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2311 if (!NT_SUCCESS(Status))
2312 {
2313 DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex);
2314 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2315 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2316 ReleaseAttributeContext(IndexRootContext);
2317 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2318 return Status;
2319 }
2320
2321 // Write the new index root to disk
2322 Status = WriteAttribute(DeviceExt,
2323 IndexRootContext,
2324 0,
2325 (PUCHAR)NewIndexRoot,
2326 AttributeLength,
2327 &LengthWritten,
2328 ParentFileRecord);
2329 if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength)
2330 {
2331 DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
2332 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2333 ReleaseAttributeContext(IndexRootContext);
2334 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2335 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2336 return Status;
2337 }
2338
2339 // re-read the parent file record, so we can dump it
2340 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2341 if (!NT_SUCCESS(Status))
2342 {
2343 DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n");
2344 }
2345 else
2346 {
2347 DPRINT1("Dumping new parent file record:\n");
2348 NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
2349 }
2350
2351 // Cleanup
2352 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2353 ReleaseAttributeContext(IndexRootContext);
2354 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2355 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2356
2357 return Status;
2358 }
2359
2360 NTSTATUS
2361 AddFixupArray(PDEVICE_EXTENSION Vcb,
2362 PNTFS_RECORD_HEADER Record)
2363 {
2364 USHORT *pShortToFixUp;
2365 ULONG ArrayEntryCount = Record->UsaCount - 1;
2366 ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2;
2367 ULONG i;
2368
2369 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
2370
2371 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
2372
2373 fixupArray->USN++;
2374
2375 for (i = 0; i < ArrayEntryCount; i++)
2376 {
2377 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
2378
2379 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
2380 fixupArray->Array[i] = *pShortToFixUp;
2381 *pShortToFixUp = fixupArray->USN;
2382 Offset += Vcb->NtfsInfo.BytesPerSector;
2383 }
2384
2385 return STATUS_SUCCESS;
2386 }
2387
2388 NTSTATUS
2389 ReadLCN(PDEVICE_EXTENSION Vcb,
2390 ULONGLONG lcn,
2391 ULONG count,
2392 PVOID buffer)
2393 {
2394 LARGE_INTEGER DiskSector;
2395
2396 DiskSector.QuadPart = lcn;
2397
2398 return NtfsReadSectors(Vcb->StorageDevice,
2399 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
2400 count * Vcb->NtfsInfo.SectorsPerCluster,
2401 Vcb->NtfsInfo.BytesPerSector,
2402 buffer,
2403 FALSE);
2404 }
2405
2406
2407 BOOLEAN
2408 CompareFileName(PUNICODE_STRING FileName,
2409 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
2410 BOOLEAN DirSearch,
2411 BOOLEAN CaseSensitive)
2412 {
2413 BOOLEAN Ret, Alloc = FALSE;
2414 UNICODE_STRING EntryName;
2415
2416 EntryName.Buffer = IndexEntry->FileName.Name;
2417 EntryName.Length =
2418 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
2419
2420 if (DirSearch)
2421 {
2422 UNICODE_STRING IntFileName;
2423 if (!CaseSensitive)
2424 {
2425 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
2426 Alloc = TRUE;
2427 }
2428 else
2429 {
2430 IntFileName = *FileName;
2431 }
2432
2433 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL);
2434
2435 if (Alloc)
2436 {
2437 RtlFreeUnicodeString(&IntFileName);
2438 }
2439
2440 return Ret;
2441 }
2442 else
2443 {
2444 return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0);
2445 }
2446 }
2447
2448 /**
2449 * @name UpdateMftMirror
2450 * @implemented
2451 *
2452 * Backs-up the first ~4 master file table entries to the $MFTMirr file.
2453 *
2454 * @param Vcb
2455 * Pointer to an NTFS_VCB for the volume whose Mft mirror is being updated.
2456 *
2457 * @returninja livecd
2458
2459 * STATUS_SUCCESS on success.
2460 * STATUS_INSUFFICIENT_RESOURCES if an allocation failed.
2461 * STATUS_UNSUCCESSFUL if we couldn't read the master file table.
2462 *
2463 * @remarks
2464 * NTFS maintains up-to-date copies of the first several mft entries in the $MFTMirr file. Usually, the first 4 file
2465 * records from the mft are stored. The exact number of entries is determined by the size of $MFTMirr's $DATA.
2466 * If $MFTMirr is not up-to-date, chkdsk will reject every change it can find prior to when $MFTMirr was last updated.
2467 * Therefore, it's recommended to call this function if the volume changes considerably. For instance, IncreaseMftSize()
2468 * relies on this function to keep chkdsk from deleting the mft entries it creates. Note that under most instances, creating
2469 * or deleting a file will not affect the first ~four mft entries, and so will not require updating the mft mirror.
2470 */
2471 NTSTATUS
2472 UpdateMftMirror(PNTFS_VCB Vcb)
2473 {
2474 PFILE_RECORD_HEADER MirrorFileRecord;
2475 PNTFS_ATTR_CONTEXT MirrDataContext;
2476 PNTFS_ATTR_CONTEXT MftDataContext;
2477 PCHAR DataBuffer;
2478 ULONGLONG DataLength;
2479 NTSTATUS Status;
2480 ULONG BytesRead;
2481 ULONG LengthWritten;
2482
2483 // Allocate memory for the Mft mirror file record
2484 MirrorFileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2485 if (!MirrorFileRecord)
2486 {
2487 DPRINT1("Error: Failed to allocate memory for $MFTMirr!\n");
2488 return STATUS_INSUFFICIENT_RESOURCES;
2489 }
2490
2491 // Read the Mft Mirror file record
2492 Status = ReadFileRecord(Vcb, NTFS_FILE_MFTMIRR, MirrorFileRecord);
2493 if (!NT_SUCCESS(Status))
2494 {
2495 DPRINT1("ERROR: Failed to read $MFTMirr!\n");
2496 ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
2497 return Status;
2498 }
2499
2500 // Find the $DATA attribute of $MFTMirr
2501 Status = FindAttribute(Vcb, MirrorFileRecord, AttributeData, L"", 0, &MirrDataContext, NULL);
2502 if (!NT_SUCCESS(Status))
2503 {
2504 DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
2505 ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
2506 return Status;
2507 }
2508
2509 // Find the $DATA attribute of $MFT
2510 Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeData, L"", 0, &MftDataContext, NULL);
2511 if (!NT_SUCCESS(Status))
2512 {
2513 DPRINT1("ERROR: Couldn't find $DATA attribute!\n");
2514 ReleaseAttributeContext(MirrDataContext);
2515 ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
2516 return Status;
2517 }
2518
2519 // Get the size of the mirror's $DATA attribute
2520 DataLength = AttributeDataLength(MirrDataContext->pRecord);
2521
2522 ASSERT(DataLength % Vcb->NtfsInfo.BytesPerFileRecord == 0);
2523
2524 // Create buffer for the mirror's $DATA attribute
2525 DataBuffer = ExAllocatePoolWithTag(NonPagedPool, DataLength, TAG_NTFS);
2526 if (!DataBuffer)
2527 {
2528 DPRINT1("Error: Couldn't allocate memory for $DATA buffer!\n");
2529 ReleaseAttributeContext(MftDataContext);
2530 ReleaseAttributeContext(MirrDataContext);
2531 ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
2532 return STATUS_INSUFFICIENT_RESOURCES;
2533 }
2534
2535 ASSERT(DataLength < ULONG_MAX);
2536
2537 // Back up the first several entries of the Mft's $DATA Attribute
2538 BytesRead = ReadAttribute(Vcb, MftDataContext, 0, DataBuffer, (ULONG)DataLength);
2539 if (BytesRead != (ULONG)DataLength)
2540 {
2541 DPRINT1("Error: Failed to read $DATA for $MFTMirr!\n");
2542 ReleaseAttributeContext(MftDataContext);
2543 ReleaseAttributeContext(MirrDataContext);
2544 ExFreePoolWithTag(DataBuffer, TAG_NTFS);
2545 ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
2546 return STATUS_UNSUCCESSFUL;
2547 }
2548
2549 // Write the mirror's $DATA attribute
2550 Status = WriteAttribute(Vcb,
2551 MirrDataContext,
2552 0,
2553 (PUCHAR)DataBuffer,
2554 DataLength,
2555 &LengthWritten,
2556 MirrorFileRecord);
2557 if (!NT_SUCCESS(Status))
2558 {
2559 DPRINT1("ERROR: Failed to write $DATA attribute of $MFTMirr!\n");
2560 }
2561
2562 // Cleanup
2563 ReleaseAttributeContext(MftDataContext);
2564 ReleaseAttributeContext(MirrDataContext);
2565 ExFreePoolWithTag(DataBuffer, TAG_NTFS);
2566 ExFreePoolWithTag(MirrorFileRecord, TAG_NTFS);
2567
2568 return Status;
2569 }
2570
2571 #if 0
2572 static
2573 VOID
2574 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
2575 {
2576 DPRINT1("Entry: %p\n", IndexEntry);
2577 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
2578 DPRINT1("\tLength: %u\n", IndexEntry->Length);
2579 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
2580 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
2581 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
2582 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
2583 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
2584 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
2585 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
2586 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
2587 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
2588 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
2589 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
2590 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
2591 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
2592 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
2593 }
2594 #endif
2595
2596 NTSTATUS
2597 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
2598 PFILE_RECORD_HEADER MftRecord,
2599 PCHAR IndexRecord,
2600 ULONG IndexBlockSize,
2601 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
2602 PINDEX_ENTRY_ATTRIBUTE LastEntry,
2603 PUNICODE_STRING FileName,
2604 PULONG StartEntry,
2605 PULONG CurrentEntry,
2606 BOOLEAN DirSearch,
2607 BOOLEAN CaseSensitive,
2608 ULONGLONG *OutMFTIndex)
2609 {
2610 NTSTATUS Status;
2611 ULONG RecordOffset;
2612 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
2613 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
2614 ULONGLONG IndexAllocationSize;
2615 PINDEX_BUFFER IndexBuffer;
2616
2617 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
2618 Vcb,
2619 MftRecord,
2620 IndexRecord,
2621 IndexBlockSize,
2622 FirstEntry,
2623 LastEntry,
2624 FileName,
2625 *StartEntry,
2626 *CurrentEntry,
2627 DirSearch ? "TRUE" : "FALSE",
2628 CaseSensitive ? "TRUE" : "FALSE",
2629 OutMFTIndex);
2630
2631 IndexEntry = FirstEntry;
2632 while (IndexEntry < LastEntry &&
2633 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
2634 {
2635 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE &&
2636 *CurrentEntry >= *StartEntry &&
2637 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
2638 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
2639 {
2640 *StartEntry = *CurrentEntry;
2641 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
2642 return STATUS_SUCCESS;
2643 }
2644
2645 (*CurrentEntry) += 1;
2646 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
2647 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
2648 }
2649
2650 /* If we're already browsing a subnode */
2651 if (IndexRecord == NULL)
2652 {
2653 return STATUS_OBJECT_PATH_NOT_FOUND;
2654 }
2655
2656 /* If there's no subnode */
2657 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
2658 {
2659 return STATUS_OBJECT_PATH_NOT_FOUND;
2660 }
2661
2662 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
2663 if (!NT_SUCCESS(Status))
2664 {
2665 DPRINT1("Corrupted filesystem!\n");
2666 return Status;
2667 }
2668
2669 IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord);
2670 Status = STATUS_OBJECT_PATH_NOT_FOUND;
2671 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
2672 {
2673 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
2674 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
2675 if (!NT_SUCCESS(Status))
2676 {
2677 break;
2678 }
2679
2680 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
2681 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
2682 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
2683 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
2684 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
2685 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
2686
2687 Status = BrowseIndexEntries(NULL,
2688 NULL,
2689 NULL,
2690 0,
2691 FirstEntry,
2692 LastEntry,
2693 FileName,
2694 StartEntry,
2695 CurrentEntry,
2696 DirSearch,
2697 CaseSensitive,
2698 OutMFTIndex);
2699 if (NT_SUCCESS(Status))
2700 {
2701 break;
2702 }
2703 }
2704
2705 ReleaseAttributeContext(IndexAllocationCtx);
2706 return Status;
2707 }
2708
2709 NTSTATUS
2710 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
2711 ULONGLONG MFTIndex,
2712 PUNICODE_STRING FileName,
2713 PULONG FirstEntry,
2714 BOOLEAN DirSearch,
2715 BOOLEAN CaseSensitive,
2716 ULONGLONG *OutMFTIndex)
2717 {
2718 PFILE_RECORD_HEADER MftRecord;
2719 PNTFS_ATTR_CONTEXT IndexRootCtx;
2720 PINDEX_ROOT_ATTRIBUTE IndexRoot;
2721 PCHAR IndexRecord;
2722 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
2723 NTSTATUS Status;
2724 ULONG CurrentEntry = 0;
2725
2726 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n",
2727 Vcb,
2728 MFTIndex,
2729 FileName,
2730 *FirstEntry,
2731 DirSearch ? "TRUE" : "FALSE",
2732 CaseSensitive ? "TRUE" : "FALSE",
2733 OutMFTIndex);
2734
2735 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
2736 Vcb->NtfsInfo.BytesPerFileRecord,
2737 TAG_NTFS);
2738 if (MftRecord == NULL)
2739 {
2740 return STATUS_INSUFFICIENT_RESOURCES;
2741 }
2742
2743 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
2744 if (!NT_SUCCESS(Status))
2745 {
2746 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2747 return Status;
2748 }
2749
2750 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
2751 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
2752 if (!NT_SUCCESS(Status))
2753 {
2754 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2755 return Status;
2756 }
2757
2758 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
2759 if (IndexRecord == NULL)
2760 {
2761 ReleaseAttributeContext(IndexRootCtx);
2762 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2763 return STATUS_INSUFFICIENT_RESOURCES;
2764 }
2765
2766 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
2767 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
2768 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
2769 /* Index root is always resident. */
2770 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
2771 ReleaseAttributeContext(IndexRootCtx);
2772
2773 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
2774
2775 Status = BrowseIndexEntries(Vcb,
2776 MftRecord,
2777 IndexRecord,
2778 IndexRoot->SizeOfEntry,
2779 IndexEntry,
2780 IndexEntryEnd,
2781 FileName,
2782 FirstEntry,
2783 &CurrentEntry,
2784 DirSearch,
2785 CaseSensitive,
2786 OutMFTIndex);
2787
2788 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2789 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2790
2791 return Status;
2792 }
2793
2794 NTSTATUS
2795 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
2796 PUNICODE_STRING PathName,
2797 BOOLEAN CaseSensitive,
2798 PFILE_RECORD_HEADER *FileRecord,
2799 PULONGLONG MFTIndex,
2800 ULONGLONG CurrentMFTIndex)
2801 {
2802 UNICODE_STRING Current, Remaining;
2803 NTSTATUS Status;
2804 ULONG FirstEntry = 0;
2805
2806 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
2807 Vcb,
2808 PathName,
2809 CaseSensitive ? "TRUE" : "FALSE",
2810 FileRecord,
2811 MFTIndex,
2812 CurrentMFTIndex);
2813
2814 FsRtlDissectName(*PathName, &Current, &Remaining);
2815
2816 while (Current.Length != 0)
2817 {
2818 DPRINT("Current: %wZ\n", &Current);
2819
2820 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex);
2821 if (!NT_SUCCESS(Status))
2822 {
2823 return Status;
2824 }
2825
2826 if (Remaining.Length == 0)
2827 break;
2828
2829 FsRtlDissectName(Current, &Current, &Remaining);
2830 }
2831
2832 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2833 if (*FileRecord == NULL)
2834 {
2835 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
2836 return STATUS_INSUFFICIENT_RESOURCES;
2837 }
2838
2839 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2840 if (!NT_SUCCESS(Status))
2841 {
2842 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
2843 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2844 return Status;
2845 }
2846
2847 *MFTIndex = CurrentMFTIndex;
2848
2849 return STATUS_SUCCESS;
2850 }
2851
2852 NTSTATUS
2853 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
2854 PUNICODE_STRING PathName,
2855 BOOLEAN CaseSensitive,
2856 PFILE_RECORD_HEADER *FileRecord,
2857 PULONGLONG MFTIndex)
2858 {
2859 return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT);
2860 }
2861
2862 /**
2863 * @name NtfsDumpFileRecord
2864 * @implemented
2865 *
2866 * Provides diagnostic information about a file record. Prints a hex dump
2867 * of the entire record (based on the size reported by FileRecord->ByesInUse),
2868 * then prints a dump of each attribute.
2869 *
2870 * @param Vcb
2871 * Pointer to a DEVICE_EXTENSION describing the volume.
2872 *
2873 * @param FileRecord
2874 * Pointer to the file record to be analyzed.
2875 *
2876 * @remarks
2877 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
2878 * in size, and not just the header.
2879 *
2880 */
2881 VOID
2882 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
2883 PFILE_RECORD_HEADER FileRecord)
2884 {
2885 ULONG i, j;
2886
2887 // dump binary data, 8 bytes at a time
2888 for (i = 0; i < FileRecord->BytesInUse; i += 8)
2889 {
2890 // display current offset, in hex
2891 DbgPrint("\t%03x\t", i);
2892
2893 // display hex value of each of the next 8 bytes
2894 for (j = 0; j < 8; j++)
2895 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
2896 DbgPrint("\n");
2897 }
2898
2899 NtfsDumpFileAttributes(Vcb, FileRecord);
2900 }
2901
2902 NTSTATUS
2903 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
2904 PUNICODE_STRING SearchPattern,
2905 PULONG FirstEntry,
2906 PFILE_RECORD_HEADER *FileRecord,
2907 PULONGLONG MFTIndex,
2908 ULONGLONG CurrentMFTIndex,
2909 BOOLEAN CaseSensitive)
2910 {
2911 NTSTATUS Status;
2912
2913 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n",
2914 Vcb,
2915 SearchPattern,
2916 *FirstEntry,
2917 FileRecord,
2918 MFTIndex,
2919 CurrentMFTIndex,
2920 (CaseSensitive ? "TRUE" : "FALSE"));
2921
2922 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, CaseSensitive, &CurrentMFTIndex);
2923 if (!NT_SUCCESS(Status))
2924 {
2925 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
2926 return Status;
2927 }
2928
2929 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2930 if (*FileRecord == NULL)
2931 {
2932 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
2933 return STATUS_INSUFFICIENT_RESOURCES;
2934 }
2935
2936 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2937 if (!NT_SUCCESS(Status))
2938 {
2939 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
2940 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2941 return Status;
2942 }
2943
2944 *MFTIndex = CurrentMFTIndex;
2945
2946 return STATUS_SUCCESS;
2947 }
2948
2949 /* EOF */