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