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