[NTFS] - Add some improvements to B-Tree support. Add preliminary support for trees...
[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
34 #define NDEBUG
35 #include <debug.h>
36
37 /* FUNCTIONS ****************************************************************/
38
39 PNTFS_ATTR_CONTEXT
40 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
41 {
42 PNTFS_ATTR_CONTEXT Context;
43
44 Context = ExAllocatePoolWithTag(NonPagedPool,
45 FIELD_OFFSET(NTFS_ATTR_CONTEXT, Record) + AttrRecord->Length,
46 TAG_NTFS);
47 RtlCopyMemory(&Context->Record, AttrRecord, AttrRecord->Length);
48 if (AttrRecord->IsNonResident)
49 {
50 LONGLONG DataRunOffset;
51 ULONGLONG DataRunLength;
52 ULONGLONG NextVBN = 0;
53 PUCHAR DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
54
55 Context->CacheRun = DataRun;
56 Context->CacheRunOffset = 0;
57 Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
58 Context->CacheRunLength = DataRunLength;
59 if (DataRunOffset != -1)
60 {
61 /* Normal run. */
62 Context->CacheRunStartLCN =
63 Context->CacheRunLastLCN = DataRunOffset;
64 }
65 else
66 {
67 /* Sparse run. */
68 Context->CacheRunStartLCN = -1;
69 Context->CacheRunLastLCN = 0;
70 }
71 Context->CacheRunCurrentOffset = 0;
72
73 // Convert the data runs to a map control block
74 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN)))
75 {
76 DPRINT1("Unable to convert data runs to MCB!\n");
77 ExFreePoolWithTag(Context, TAG_NTFS);
78 return NULL;
79 }
80 }
81
82 return Context;
83 }
84
85
86 VOID
87 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
88 {
89 if (Context->Record.IsNonResident)
90 {
91 FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
92 }
93
94 ExFreePoolWithTag(Context, TAG_NTFS);
95 }
96
97
98 /**
99 * @name FindAttribute
100 * @implemented
101 *
102 * Searches a file record for an attribute matching the given type and name.
103 *
104 * @param Offset
105 * Optional pointer to a ULONG that will receive the offset of the found attribute
106 * from the beginning of the record. Can be set to NULL.
107 */
108 NTSTATUS
109 FindAttribute(PDEVICE_EXTENSION Vcb,
110 PFILE_RECORD_HEADER MftRecord,
111 ULONG Type,
112 PCWSTR Name,
113 ULONG NameLength,
114 PNTFS_ATTR_CONTEXT * AttrCtx,
115 PULONG Offset)
116 {
117 BOOLEAN Found;
118 NTSTATUS Status;
119 FIND_ATTR_CONTXT Context;
120 PNTFS_ATTR_RECORD Attribute;
121
122 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %lu, %p, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx, Offset);
123
124 Found = FALSE;
125 Status = FindFirstAttribute(&Context, Vcb, MftRecord, FALSE, &Attribute);
126 while (NT_SUCCESS(Status))
127 {
128 if (Attribute->Type == Type && Attribute->NameLength == NameLength)
129 {
130 if (NameLength != 0)
131 {
132 PWCHAR AttrName;
133
134 AttrName = (PWCHAR)((PCHAR)Attribute + Attribute->NameOffset);
135 DPRINT("%.*S, %.*S\n", Attribute->NameLength, AttrName, NameLength, Name);
136 if (RtlCompareMemory(AttrName, Name, NameLength * sizeof(WCHAR)) == (NameLength * sizeof(WCHAR)))
137 {
138 Found = TRUE;
139 }
140 }
141 else
142 {
143 Found = TRUE;
144 }
145
146 if (Found)
147 {
148 /* Found it, fill up the context and return. */
149 DPRINT("Found context\n");
150 *AttrCtx = PrepareAttributeContext(Attribute);
151
152 (*AttrCtx)->FileMFTIndex = MftRecord->MFTRecordNumber;
153
154 if (Offset != NULL)
155 *Offset = Context.Offset;
156
157 FindCloseAttribute(&Context);
158 return STATUS_SUCCESS;
159 }
160 }
161
162 Status = FindNextAttribute(&Context, &Attribute);
163 }
164
165 FindCloseAttribute(&Context);
166 return STATUS_OBJECT_NAME_NOT_FOUND;
167 }
168
169
170 ULONGLONG
171 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord)
172 {
173 if (AttrRecord->IsNonResident)
174 return AttrRecord->NonResident.AllocatedSize;
175 else
176 return AttrRecord->Resident.ValueLength;
177 }
178
179
180 ULONGLONG
181 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
182 {
183 if (AttrRecord->IsNonResident)
184 return AttrRecord->NonResident.DataSize;
185 else
186 return AttrRecord->Resident.ValueLength;
187 }
188
189 /**
190 * @name IncreaseMftSize
191 * @implemented
192 *
193 * Increases the size of the master file table on a volume, increasing the space available for file records.
194 *
195 * @param Vcb
196 * Pointer to the VCB (DEVICE_EXTENSION) of the target volume.
197 *
198 *
199 * @param CanWait
200 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
201 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
202 *
203 * @return
204 * STATUS_SUCCESS on success.
205 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
206 * STATUS_INVALID_PARAMETER if there was an error reading the Mft's bitmap.
207 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
208 *
209 * @remarks
210 * Increases the size of the Master File Table by 8 records. Bitmap entries for the new records are cleared,
211 * and the bitmap is also enlarged if needed. Mimicking Windows' behavior when enlarging the mft is still TODO.
212 * This function will wait for exlusive access to the volume fcb.
213 */
214 NTSTATUS
215 IncreaseMftSize(PDEVICE_EXTENSION Vcb, BOOLEAN CanWait)
216 {
217 PNTFS_ATTR_CONTEXT BitmapContext;
218 LARGE_INTEGER BitmapSize;
219 LARGE_INTEGER DataSize;
220 LONGLONG BitmapSizeDifference;
221 ULONG DataSizeDifference = Vcb->NtfsInfo.BytesPerFileRecord * 8;
222 ULONG BitmapOffset;
223 PUCHAR BitmapBuffer;
224 ULONGLONG BitmapBytes;
225 ULONGLONG NewBitmapSize;
226 ULONG BytesRead;
227 ULONG LengthWritten;
228 NTSTATUS Status;
229
230 DPRINT1("IncreaseMftSize(%p, %s)\n", Vcb, CanWait ? "TRUE" : "FALSE");
231
232 // We need exclusive access to the mft while we change its size
233 if (!ExAcquireResourceExclusiveLite(&(Vcb->DirResource), CanWait))
234 {
235 return STATUS_CANT_WAIT;
236 }
237
238 // Find the bitmap attribute of master file table
239 Status = FindAttribute(Vcb, Vcb->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, &BitmapOffset);
240 if (!NT_SUCCESS(Status))
241 {
242 DPRINT1("ERROR: Couldn't find $BITMAP attribute of Mft!\n");
243 ExReleaseResourceLite(&(Vcb->DirResource));
244 return Status;
245 }
246
247 // Get size of Bitmap Attribute
248 BitmapSize.QuadPart = AttributeDataLength(&BitmapContext->Record);
249
250 // Calculate the new mft size
251 DataSize.QuadPart = AttributeDataLength(&(Vcb->MFTContext->Record)) + DataSizeDifference;
252
253 // Determine how many bytes will make up the bitmap
254 BitmapBytes = DataSize.QuadPart / Vcb->NtfsInfo.BytesPerFileRecord / 8;
255
256 // Determine how much we need to adjust the bitmap size (it's possible we don't)
257 BitmapSizeDifference = BitmapBytes - BitmapSize.QuadPart;
258 NewBitmapSize = max(BitmapSize.QuadPart + BitmapSizeDifference, BitmapSize.QuadPart);
259
260 // Allocate memory for the bitmap
261 BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, NewBitmapSize, TAG_NTFS);
262 if (!BitmapBuffer)
263 {
264 DPRINT1("ERROR: Unable to allocate memory for bitmap attribute!\n");
265 ExReleaseResourceLite(&(Vcb->DirResource));
266 ReleaseAttributeContext(BitmapContext);
267 return STATUS_INSUFFICIENT_RESOURCES;
268 }
269
270 // Zero the bytes we'll be adding
271 RtlZeroMemory(BitmapBuffer, NewBitmapSize);
272
273 // Read the bitmap attribute
274 BytesRead = ReadAttribute(Vcb,
275 BitmapContext,
276 0,
277 (PCHAR)BitmapBuffer,
278 BitmapSize.LowPart);
279 if (BytesRead != BitmapSize.LowPart)
280 {
281 DPRINT1("ERROR: Bytes read != Bitmap size!\n");
282 ExReleaseResourceLite(&(Vcb->DirResource));
283 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
284 ReleaseAttributeContext(BitmapContext);
285 return STATUS_INVALID_PARAMETER;
286 }
287
288 // Increase the mft size
289 Status = SetNonResidentAttributeDataLength(Vcb, Vcb->MFTContext, Vcb->MftDataOffset, Vcb->MasterFileTable, &DataSize);
290 if (!NT_SUCCESS(Status))
291 {
292 DPRINT1("ERROR: Failed to set size of $MFT data attribute!\n");
293 ExReleaseResourceLite(&(Vcb->DirResource));
294 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
295 ReleaseAttributeContext(BitmapContext);
296 return Status;
297 }
298
299 // If the bitmap grew
300 if (BitmapSizeDifference > 0)
301 {
302 // Set the new bitmap size
303 BitmapSize.QuadPart += BitmapSizeDifference;
304 if (BitmapContext->Record.IsNonResident)
305 Status = SetNonResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
306 else
307 Status = SetResidentAttributeDataLength(Vcb, BitmapContext, BitmapOffset, Vcb->MasterFileTable, &BitmapSize);
308
309 if (!NT_SUCCESS(Status))
310 {
311 DPRINT1("ERROR: Failed to set size of bitmap attribute!\n");
312 ExReleaseResourceLite(&(Vcb->DirResource));
313 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
314 ReleaseAttributeContext(BitmapContext);
315 return Status;
316 }
317 }
318
319 //NtfsDumpFileAttributes(Vcb, FileRecord);
320
321 // Update the file record with the new attribute sizes
322 Status = UpdateFileRecord(Vcb, Vcb->VolumeFcb->MFTIndex, Vcb->MasterFileTable);
323 if (!NT_SUCCESS(Status))
324 {
325 DPRINT1("ERROR: Failed to update $MFT file record!\n");
326 ExReleaseResourceLite(&(Vcb->DirResource));
327 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
328 ReleaseAttributeContext(BitmapContext);
329 return Status;
330 }
331
332 // Write out the new bitmap
333 Status = WriteAttribute(Vcb, BitmapContext, BitmapOffset, BitmapBuffer, BitmapSize.LowPart, &LengthWritten);
334 if (!NT_SUCCESS(Status))
335 {
336 ExReleaseResourceLite(&(Vcb->DirResource));
337 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
338 ReleaseAttributeContext(BitmapContext);
339 DPRINT1("ERROR: Couldn't write to bitmap attribute of $MFT!\n");
340 return Status;
341 }
342
343 // Cleanup
344 ExReleaseResourceLite(&(Vcb->DirResource));
345 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
346 ReleaseAttributeContext(BitmapContext);
347
348 return STATUS_SUCCESS;
349 }
350
351 VOID
352 InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
353 PFILE_RECORD_HEADER FileRecord,
354 ULONG AttrOffset,
355 ULONG DataSize)
356 {
357 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
358 ULONG NextAttributeOffset;
359
360 DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize);
361
362 ASSERT(!AttrContext->Record.IsNonResident);
363
364 // update ValueLength Field
365 AttrContext->Record.Resident.ValueLength =
366 Destination->Resident.ValueLength = DataSize;
367
368 // calculate the record length and end marker offset
369 AttrContext->Record.Length =
370 Destination->Length = DataSize + AttrContext->Record.Resident.ValueOffset;
371 NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
372
373 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
374 if (NextAttributeOffset % 8 != 0)
375 {
376 USHORT Padding = ATTR_RECORD_ALIGNMENT - (NextAttributeOffset % ATTR_RECORD_ALIGNMENT);
377 NextAttributeOffset += Padding;
378 AttrContext->Record.Length += Padding;
379 Destination->Length += Padding;
380 }
381
382 // advance Destination to the final "attribute" and set the file record end
383 Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
384 SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END);
385 }
386
387 /**
388 * @parameter FileRecord
389 * Pointer to a file record. Must be a full record at least
390 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
391 */
392 NTSTATUS
393 SetAttributeDataLength(PFILE_OBJECT FileObject,
394 PNTFS_FCB Fcb,
395 PNTFS_ATTR_CONTEXT AttrContext,
396 ULONG AttrOffset,
397 PFILE_RECORD_HEADER FileRecord,
398 PLARGE_INTEGER DataSize)
399 {
400 NTSTATUS Status = STATUS_SUCCESS;
401
402 DPRINT1("SetAttributeDataLenth(%p, %p, %p, %lu, %p, %I64u)\n",
403 FileObject,
404 Fcb,
405 AttrContext,
406 AttrOffset,
407 FileRecord,
408 DataSize->QuadPart);
409
410 // are we truncating the file?
411 if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record))
412 {
413 if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize))
414 {
415 DPRINT1("Can't truncate a memory-mapped file!\n");
416 return STATUS_USER_MAPPED_FILE;
417 }
418 }
419
420 if (AttrContext->Record.IsNonResident)
421 {
422 Status = SetNonResidentAttributeDataLength(Fcb->Vcb,
423 AttrContext,
424 AttrOffset,
425 FileRecord,
426 DataSize);
427 }
428 else
429 {
430 // resident attribute
431 Status = SetResidentAttributeDataLength(Fcb->Vcb,
432 AttrContext,
433 AttrOffset,
434 FileRecord,
435 DataSize);
436 }
437
438 if (!NT_SUCCESS(Status))
439 {
440 DPRINT1("ERROR: Failed to set size of attribute!\n");
441 return Status;
442 }
443
444 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
445
446 // write the updated file record back to disk
447 Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord);
448
449 if (NT_SUCCESS(Status))
450 {
451 if (AttrContext->Record.IsNonResident)
452 Fcb->RFCB.AllocationSize.QuadPart = AttrContext->Record.NonResident.AllocatedSize;
453 else
454 Fcb->RFCB.AllocationSize = *DataSize;
455 Fcb->RFCB.FileSize = *DataSize;
456 Fcb->RFCB.ValidDataLength = *DataSize;
457 CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
458 }
459
460 return STATUS_SUCCESS;
461 }
462
463 /**
464 * @name SetFileRecordEnd
465 * @implemented
466 *
467 * This small function sets a new endpoint for the file record. It set's the final
468 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
469 *
470 * @param FileRecord
471 * Pointer to the file record whose endpoint (length) will be set.
472 *
473 * @param AttrEnd
474 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
475 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
476 *
477 * @param EndMarker
478 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
479 * a file record, it preserves the final ULONG that previously ended the record, even though this
480 * value is (to my knowledge) never used. We emulate this behavior.
481 *
482 */
483 VOID
484 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
485 PNTFS_ATTR_RECORD AttrEnd,
486 ULONG EndMarker)
487 {
488 // mark the end of attributes
489 AttrEnd->Type = AttributeEnd;
490
491 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
492 AttrEnd->Length = EndMarker;
493
494 // recalculate bytes in use
495 FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2;
496 }
497
498 /**
499 * @name SetNonResidentAttributeDataLength
500 * @implemented
501 *
502 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
503 *
504 * @param Vcb
505 * Pointer to a DEVICE_EXTENSION describing the target disk.
506 *
507 * @param AttrContext
508 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
509 *
510 * @param AttrOffset
511 * Offset, from the beginning of the record, of the attribute being sized.
512 *
513 * @param FileRecord
514 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
515 * not just the header.
516 *
517 * @param DataSize
518 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
519 *
520 * @return
521 * STATUS_SUCCESS on success;
522 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
523 * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
524 *
525 * @remarks
526 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
527 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
528 * any associated files. Synchronization is the callers responsibility.
529 */
530 NTSTATUS
531 SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
532 PNTFS_ATTR_CONTEXT AttrContext,
533 ULONG AttrOffset,
534 PFILE_RECORD_HEADER FileRecord,
535 PLARGE_INTEGER DataSize)
536 {
537 NTSTATUS Status = STATUS_SUCCESS;
538 ULONG BytesPerCluster = Vcb->NtfsInfo.BytesPerCluster;
539 ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster);
540 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
541 ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster;
542
543 ASSERT(AttrContext->Record.IsNonResident);
544
545 // do we need to increase the allocation size?
546 if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize)
547 {
548 ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters;
549 LARGE_INTEGER LastClusterInDataRun;
550 ULONG NextAssignedCluster;
551 ULONG AssignedClusters;
552
553 if (ExistingClusters == 0)
554 {
555 LastClusterInDataRun.QuadPart = 0;
556 }
557 else
558 {
559 if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
560 (LONGLONG)AttrContext->Record.NonResident.HighestVCN,
561 (PLONGLONG)&LastClusterInDataRun.QuadPart,
562 NULL,
563 NULL,
564 NULL,
565 NULL))
566 {
567 DPRINT1("Error looking up final large MCB entry!\n");
568
569 // Most likely, HighestVCN went above the largest mapping
570 DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
571 return STATUS_INVALID_PARAMETER;
572 }
573 }
574
575 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
576 DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
577
578 while (ClustersNeeded > 0)
579 {
580 Status = NtfsAllocateClusters(Vcb,
581 LastClusterInDataRun.LowPart + 1,
582 ClustersNeeded,
583 &NextAssignedCluster,
584 &AssignedClusters);
585
586 if (!NT_SUCCESS(Status))
587 {
588 DPRINT1("Error: Unable to allocate requested clusters!\n");
589 return Status;
590 }
591
592 // now we need to add the clusters we allocated to the data run
593 Status = AddRun(Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters);
594 if (!NT_SUCCESS(Status))
595 {
596 DPRINT1("Error: Unable to add data run!\n");
597 return Status;
598 }
599
600 ClustersNeeded -= AssignedClusters;
601 LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1;
602 }
603 }
604 else if (AttrContext->Record.NonResident.AllocatedSize > AllocationSize)
605 {
606 // shrink allocation size
607 ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster);
608 Status = FreeClusters(Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree);
609 }
610
611 // TODO: is the file compressed, encrypted, or sparse?
612
613 AttrContext->Record.NonResident.AllocatedSize = AllocationSize;
614 AttrContext->Record.NonResident.DataSize = DataSize->QuadPart;
615 AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
616
617 DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
618 DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
619 DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
620
621 DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
622
623 return Status;
624 }
625
626 /**
627 * @name SetResidentAttributeDataLength
628 * @implemented
629 *
630 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
631 *
632 * @param Vcb
633 * Pointer to a DEVICE_EXTENSION describing the target disk.
634 *
635 * @param AttrContext
636 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
637 *
638 * @param AttrOffset
639 * Offset, from the beginning of the record, of the attribute being sized.
640 *
641 * @param FileRecord
642 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
643 * not just the header.
644 *
645 * @param DataSize
646 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
647 *
648 * @return
649 * STATUS_SUCCESS on success;
650 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
651 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
652 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
653 * last attribute listed in the file record.
654 *
655 * @remarks
656 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
657 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
658 * any associated files. Synchronization is the callers responsibility.
659 */
660 NTSTATUS
661 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
662 PNTFS_ATTR_CONTEXT AttrContext,
663 ULONG AttrOffset,
664 PFILE_RECORD_HEADER FileRecord,
665 PLARGE_INTEGER DataSize)
666 {
667 NTSTATUS Status;
668
669 // find the next attribute
670 ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
671 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
672
673 ASSERT(!AttrContext->Record.IsNonResident);
674
675 //NtfsDumpFileAttributes(Vcb, FileRecord);
676
677 // Do we need to increase the data length?
678 if (DataSize->QuadPart > AttrContext->Record.Resident.ValueLength)
679 {
680 // There's usually padding at the end of a record. Do we need to extend past it?
681 ULONG MaxValueLength = AttrContext->Record.Length - AttrContext->Record.Resident.ValueOffset;
682 if (MaxValueLength < DataSize->LowPart)
683 {
684 // If this is the last attribute, we could move the end marker to the very end of the file record
685 MaxValueLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
686
687 if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
688 {
689 // convert attribute to non-resident
690 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
691 LARGE_INTEGER AttribDataSize;
692 PVOID AttribData;
693 ULONG EndAttributeOffset;
694 ULONG LengthWritten;
695
696 DPRINT1("Converting attribute to non-resident.\n");
697
698 AttribDataSize.QuadPart = AttrContext->Record.Resident.ValueLength;
699
700 // Is there existing data we need to back-up?
701 if (AttribDataSize.QuadPart > 0)
702 {
703 AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
704 if (AttribData == NULL)
705 {
706 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
707 return STATUS_INSUFFICIENT_RESOURCES;
708 }
709
710 // read data to temp buffer
711 Status = ReadAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
712 if (!NT_SUCCESS(Status))
713 {
714 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
715 ExFreePoolWithTag(AttribData, TAG_NTFS);
716 return Status;
717 }
718 }
719
720 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
721
722 // Zero out the NonResident structure
723 RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN,
724 FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
725 RtlZeroMemory(&Destination->NonResident.LowestVCN,
726 FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
727
728 // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
729 AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2);
730
731 // update the end of the file record
732 // calculate position of end markers (1 byte for empty data run)
733 EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1;
734 EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, ATTR_RECORD_ALIGNMENT);
735
736 // Update the length
737 Destination->Length = EndAttributeOffset - AttrOffset;
738 AttrContext->Record.Length = Destination->Length;
739
740 // Update the file record end
741 SetFileRecordEnd(FileRecord,
742 (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset),
743 FILE_RECORD_END);
744
745 // Initialize the MCB, potentially catch an exception
746 _SEH2_TRY
747 {
748 FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
749 }
750 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
751 {
752 DPRINT1("Unable to create LargeMcb!\n");
753 if (AttribDataSize.QuadPart > 0)
754 ExFreePoolWithTag(AttribData, TAG_NTFS);
755 _SEH2_YIELD(return _SEH2_GetExceptionCode());
756 } _SEH2_END;
757
758 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized)
759 AttrContext->Record.IsNonResident = Destination->IsNonResident = 1;
760
761 // Update file record on disk
762 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
763 if (!NT_SUCCESS(Status))
764 {
765 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
766 if (AttribDataSize.QuadPart > 0)
767 ExFreePoolWithTag(AttribData, TAG_NTFS);
768 return Status;
769 }
770
771 // Now we can treat the attribute as non-resident and enlarge it normally
772 Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize);
773 if (!NT_SUCCESS(Status))
774 {
775 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
776 if (AttribDataSize.QuadPart > 0)
777 ExFreePoolWithTag(AttribData, TAG_NTFS);
778 return Status;
779 }
780
781 // restore the back-up attribute, if we made one
782 if (AttribDataSize.QuadPart > 0)
783 {
784 Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
785 if (!NT_SUCCESS(Status))
786 {
787 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
788 // TODO: Reverse migration so no data is lost
789 ExFreePoolWithTag(AttribData, TAG_NTFS);
790 return Status;
791 }
792
793 ExFreePoolWithTag(AttribData, TAG_NTFS);
794 }
795 }
796 }
797 }
798 else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength)
799 {
800 // we need to decrease the length
801 if (NextAttribute->Type != AttributeEnd)
802 {
803 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
804 return STATUS_NOT_IMPLEMENTED;
805 }
806 }
807
808 // set the new length of the resident attribute (if we didn't migrate it)
809 if (!AttrContext->Record.IsNonResident)
810 InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
811
812 return STATUS_SUCCESS;
813 }
814
815 ULONG
816 ReadAttribute(PDEVICE_EXTENSION Vcb,
817 PNTFS_ATTR_CONTEXT Context,
818 ULONGLONG Offset,
819 PCHAR Buffer,
820 ULONG Length)
821 {
822 ULONGLONG LastLCN;
823 PUCHAR DataRun;
824 LONGLONG DataRunOffset;
825 ULONGLONG DataRunLength;
826 LONGLONG DataRunStartLCN;
827 ULONGLONG CurrentOffset;
828 ULONG ReadLength;
829 ULONG AlreadyRead;
830 NTSTATUS Status;
831
832 //TEMPTEMP
833 PUCHAR TempBuffer;
834
835 if (!Context->Record.IsNonResident)
836 {
837 if (Offset > Context->Record.Resident.ValueLength)
838 return 0;
839 if (Offset + Length > Context->Record.Resident.ValueLength)
840 Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
841 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
842 return Length;
843 }
844
845 /*
846 * Non-resident attribute
847 */
848
849 /*
850 * I. Find the corresponding start data run.
851 */
852
853 AlreadyRead = 0;
854
855 // FIXME: Cache seems to be non-working. Disable it for now
856 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
857 if (0)
858 {
859 DataRun = Context->CacheRun;
860 LastLCN = Context->CacheRunLastLCN;
861 DataRunStartLCN = Context->CacheRunStartLCN;
862 DataRunLength = Context->CacheRunLength;
863 CurrentOffset = Context->CacheRunCurrentOffset;
864 }
865 else
866 {
867 //TEMPTEMP
868 ULONG UsedBufferSize;
869 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
870
871 LastLCN = 0;
872 CurrentOffset = 0;
873
874 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
875 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
876 TempBuffer,
877 Vcb->NtfsInfo.BytesPerFileRecord,
878 &UsedBufferSize);
879
880 DataRun = TempBuffer;
881
882 while (1)
883 {
884 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
885 if (DataRunOffset != -1)
886 {
887 /* Normal data run. */
888 DataRunStartLCN = LastLCN + DataRunOffset;
889 LastLCN = DataRunStartLCN;
890 }
891 else
892 {
893 /* Sparse data run. */
894 DataRunStartLCN = -1;
895 }
896
897 if (Offset >= CurrentOffset &&
898 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
899 {
900 break;
901 }
902
903 if (*DataRun == 0)
904 {
905 return AlreadyRead;
906 }
907
908 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
909 }
910 }
911
912 /*
913 * II. Go through the run list and read the data
914 */
915
916 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
917 if (DataRunStartLCN == -1)
918 {
919 RtlZeroMemory(Buffer, ReadLength);
920 Status = STATUS_SUCCESS;
921 }
922 else
923 {
924 Status = NtfsReadDisk(Vcb->StorageDevice,
925 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
926 ReadLength,
927 Vcb->NtfsInfo.BytesPerSector,
928 (PVOID)Buffer,
929 FALSE);
930 }
931 if (NT_SUCCESS(Status))
932 {
933 Length -= ReadLength;
934 Buffer += ReadLength;
935 AlreadyRead += ReadLength;
936
937 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
938 {
939 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
940 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
941 if (DataRunOffset != (ULONGLONG)-1)
942 {
943 DataRunStartLCN = LastLCN + DataRunOffset;
944 LastLCN = DataRunStartLCN;
945 }
946 else
947 DataRunStartLCN = -1;
948 }
949
950 while (Length > 0)
951 {
952 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
953 if (DataRunStartLCN == -1)
954 RtlZeroMemory(Buffer, ReadLength);
955 else
956 {
957 Status = NtfsReadDisk(Vcb->StorageDevice,
958 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
959 ReadLength,
960 Vcb->NtfsInfo.BytesPerSector,
961 (PVOID)Buffer,
962 FALSE);
963 if (!NT_SUCCESS(Status))
964 break;
965 }
966
967 Length -= ReadLength;
968 Buffer += ReadLength;
969 AlreadyRead += ReadLength;
970
971 /* We finished this request, but there still data in this data run. */
972 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
973 break;
974
975 /*
976 * Go to next run in the list.
977 */
978
979 if (*DataRun == 0)
980 break;
981 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
982 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
983 if (DataRunOffset != -1)
984 {
985 /* Normal data run. */
986 DataRunStartLCN = LastLCN + DataRunOffset;
987 LastLCN = DataRunStartLCN;
988 }
989 else
990 {
991 /* Sparse data run. */
992 DataRunStartLCN = -1;
993 }
994 } /* while */
995
996 } /* if Disk */
997
998 // TEMPTEMP
999 if (Context->Record.IsNonResident)
1000 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1001
1002 Context->CacheRun = DataRun;
1003 Context->CacheRunOffset = Offset + AlreadyRead;
1004 Context->CacheRunStartLCN = DataRunStartLCN;
1005 Context->CacheRunLength = DataRunLength;
1006 Context->CacheRunLastLCN = LastLCN;
1007 Context->CacheRunCurrentOffset = CurrentOffset;
1008
1009 return AlreadyRead;
1010 }
1011
1012
1013 /**
1014 * @name WriteAttribute
1015 * @implemented
1016 *
1017 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1018 * and it still needs more documentation / cleaning up.
1019 *
1020 * @param Vcb
1021 * Volume Control Block indicating which volume to write the attribute to
1022 *
1023 * @param Context
1024 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1025 *
1026 * @param Offset
1027 * Offset, in bytes, from the beginning of the attribute indicating where to start
1028 * writing data
1029 *
1030 * @param Buffer
1031 * The data that's being written to the device
1032 *
1033 * @param Length
1034 * How much data will be written, in bytes
1035 *
1036 * @param RealLengthWritten
1037 * Pointer to a ULONG which will receive how much data was written, in bytes
1038 *
1039 * @return
1040 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1041 * writing to a sparse file.
1042 *
1043 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1044 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1045 *
1046 */
1047
1048 NTSTATUS
1049 WriteAttribute(PDEVICE_EXTENSION Vcb,
1050 PNTFS_ATTR_CONTEXT Context,
1051 ULONGLONG Offset,
1052 const PUCHAR Buffer,
1053 ULONG Length,
1054 PULONG RealLengthWritten)
1055 {
1056 ULONGLONG LastLCN;
1057 PUCHAR DataRun;
1058 LONGLONG DataRunOffset;
1059 ULONGLONG DataRunLength;
1060 LONGLONG DataRunStartLCN;
1061 ULONGLONG CurrentOffset;
1062 ULONG WriteLength;
1063 NTSTATUS Status;
1064 PUCHAR SourceBuffer = Buffer;
1065 LONGLONG StartingOffset;
1066
1067 //TEMPTEMP
1068 PUCHAR TempBuffer;
1069
1070
1071 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten);
1072
1073 *RealLengthWritten = 0;
1074
1075 // is this a resident attribute?
1076 if (!Context->Record.IsNonResident)
1077 {
1078 ULONG AttributeOffset;
1079 PNTFS_ATTR_CONTEXT FoundContext;
1080 PFILE_RECORD_HEADER FileRecord;
1081
1082 if (Offset + Length > Context->Record.Resident.ValueLength)
1083 {
1084 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1085 return STATUS_INVALID_PARAMETER;
1086 }
1087
1088 FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1089
1090 if (!FileRecord)
1091 {
1092 DPRINT1("Error: Couldn't allocate file record!\n");
1093 return STATUS_NO_MEMORY;
1094 }
1095
1096 // read the file record
1097 ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1098
1099 // find where to write the attribute data to
1100 Status = FindAttribute(Vcb, FileRecord,
1101 Context->Record.Type,
1102 (PCWSTR)((PCHAR)&Context->Record + Context->Record.NameOffset),
1103 Context->Record.NameLength,
1104 &FoundContext,
1105 &AttributeOffset);
1106
1107 if (!NT_SUCCESS(Status))
1108 {
1109 DPRINT1("ERROR: Couldn't find matching attribute!\n");
1110 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1111 return Status;
1112 }
1113
1114 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->Record.Resident.ValueLength);
1115 Offset += AttributeOffset + Context->Record.Resident.ValueOffset;
1116
1117 if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord)
1118 {
1119 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1120 ReleaseAttributeContext(FoundContext);
1121 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1122 return STATUS_INVALID_PARAMETER;
1123 }
1124
1125 // copy the data being written into the file record
1126 RtlCopyMemory((PCHAR)FileRecord + Offset, Buffer, Length);
1127
1128 Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1129
1130 ReleaseAttributeContext(FoundContext);
1131 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1132
1133 if (NT_SUCCESS(Status))
1134 *RealLengthWritten = Length;
1135
1136 return Status;
1137 }
1138
1139 // This is a non-resident attribute.
1140
1141 // I. Find the corresponding start data run.
1142
1143 // FIXME: Cache seems to be non-working. Disable it for now
1144 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1145 /*if (0)
1146 {
1147 DataRun = Context->CacheRun;
1148 LastLCN = Context->CacheRunLastLCN;
1149 DataRunStartLCN = Context->CacheRunStartLCN;
1150 DataRunLength = Context->CacheRunLength;
1151 CurrentOffset = Context->CacheRunCurrentOffset;
1152 }
1153 else*/
1154 {
1155 ULONG UsedBufferSize;
1156 LastLCN = 0;
1157 CurrentOffset = 0;
1158
1159 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1160 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1161
1162 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
1163 TempBuffer,
1164 Vcb->NtfsInfo.BytesPerFileRecord,
1165 &UsedBufferSize);
1166
1167 DataRun = TempBuffer;
1168
1169 while (1)
1170 {
1171 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1172 if (DataRunOffset != -1)
1173 {
1174 // Normal data run.
1175 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1176 DataRunStartLCN = LastLCN + DataRunOffset;
1177 LastLCN = DataRunStartLCN;
1178 }
1179 else
1180 {
1181 // Sparse data run. We can't support writing to sparse files yet
1182 // (it may require increasing the allocation size).
1183 DataRunStartLCN = -1;
1184 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1185 return STATUS_NOT_IMPLEMENTED;
1186 }
1187
1188 // Have we reached the data run we're trying to write to?
1189 if (Offset >= CurrentOffset &&
1190 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
1191 {
1192 break;
1193 }
1194
1195 if (*DataRun == 0)
1196 {
1197 // We reached the last assigned cluster
1198 // TODO: assign new clusters to the end of the file.
1199 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1200 // [We can reach here by creating a new file record when the MFT isn't large enough]
1201 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1202 return STATUS_END_OF_FILE;
1203 }
1204
1205 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1206 }
1207 }
1208
1209 // II. Go through the run list and write the data
1210
1211 /* REVIEWME -- As adapted from NtfsReadAttribute():
1212 We seem to be making a special case for the first applicable data run, but I'm not sure why.
1213 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1214
1215 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
1216
1217 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
1218
1219 // Write the data to the disk
1220 Status = NtfsWriteDisk(Vcb->StorageDevice,
1221 StartingOffset,
1222 WriteLength,
1223 Vcb->NtfsInfo.BytesPerSector,
1224 (PVOID)SourceBuffer);
1225
1226 // Did the write fail?
1227 if (!NT_SUCCESS(Status))
1228 {
1229 Context->CacheRun = DataRun;
1230 Context->CacheRunOffset = Offset;
1231 Context->CacheRunStartLCN = DataRunStartLCN;
1232 Context->CacheRunLength = DataRunLength;
1233 Context->CacheRunLastLCN = LastLCN;
1234 Context->CacheRunCurrentOffset = CurrentOffset;
1235
1236 return Status;
1237 }
1238
1239 Length -= WriteLength;
1240 SourceBuffer += WriteLength;
1241 *RealLengthWritten += WriteLength;
1242
1243 // Did we write to the end of the data run?
1244 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
1245 {
1246 // Advance to the next data run
1247 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1248 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1249
1250 if (DataRunOffset != (ULONGLONG)-1)
1251 {
1252 DataRunStartLCN = LastLCN + DataRunOffset;
1253 LastLCN = DataRunStartLCN;
1254 }
1255 else
1256 DataRunStartLCN = -1;
1257 }
1258
1259 // Do we have more data to write?
1260 while (Length > 0)
1261 {
1262 // Make sure we don't write past the end of the current data run
1263 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
1264
1265 // Are we dealing with a sparse data run?
1266 if (DataRunStartLCN == -1)
1267 {
1268 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1269 return STATUS_NOT_IMPLEMENTED;
1270 }
1271 else
1272 {
1273 // write the data to the disk
1274 Status = NtfsWriteDisk(Vcb->StorageDevice,
1275 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
1276 WriteLength,
1277 Vcb->NtfsInfo.BytesPerSector,
1278 (PVOID)SourceBuffer);
1279 if (!NT_SUCCESS(Status))
1280 break;
1281 }
1282
1283 Length -= WriteLength;
1284 SourceBuffer += WriteLength;
1285 *RealLengthWritten += WriteLength;
1286
1287 // We finished this request, but there's still data in this data run.
1288 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1289 break;
1290
1291 // Go to next run in the list.
1292
1293 if (*DataRun == 0)
1294 {
1295 // that was the last run
1296 if (Length > 0)
1297 {
1298 // Failed sanity check.
1299 DPRINT1("Encountered EOF before expected!\n");
1300 return STATUS_END_OF_FILE;
1301 }
1302
1303 break;
1304 }
1305
1306 // Advance to the next data run
1307 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1308 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1309 if (DataRunOffset != -1)
1310 {
1311 // Normal data run.
1312 DataRunStartLCN = LastLCN + DataRunOffset;
1313 LastLCN = DataRunStartLCN;
1314 }
1315 else
1316 {
1317 // Sparse data run.
1318 DataRunStartLCN = -1;
1319 }
1320 } // end while (Length > 0) [more data to write]
1321
1322 // TEMPTEMP
1323 if (Context->Record.IsNonResident)
1324 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1325
1326 Context->CacheRun = DataRun;
1327 Context->CacheRunOffset = Offset + *RealLengthWritten;
1328 Context->CacheRunStartLCN = DataRunStartLCN;
1329 Context->CacheRunLength = DataRunLength;
1330 Context->CacheRunLastLCN = LastLCN;
1331 Context->CacheRunCurrentOffset = CurrentOffset;
1332
1333 return Status;
1334 }
1335
1336 NTSTATUS
1337 ReadFileRecord(PDEVICE_EXTENSION Vcb,
1338 ULONGLONG index,
1339 PFILE_RECORD_HEADER file)
1340 {
1341 ULONGLONG BytesRead;
1342
1343 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
1344
1345 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
1346 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
1347 {
1348 DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
1349 return STATUS_PARTIAL_COPY;
1350 }
1351
1352 /* Apply update sequence array fixups. */
1353 DPRINT("Sequence number: %u\n", file->SequenceNumber);
1354 return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
1355 }
1356
1357
1358 /**
1359 * Searches a file's parent directory (given the parent's index in the mft)
1360 * for the given file. Upon finding an index entry for that file, updates
1361 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1362 *
1363 * (Most of this code was copied from NtfsFindMftRecord)
1364 */
1365 NTSTATUS
1366 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
1367 ULONGLONG ParentMFTIndex,
1368 PUNICODE_STRING FileName,
1369 BOOLEAN DirSearch,
1370 ULONGLONG NewDataSize,
1371 ULONGLONG NewAllocationSize,
1372 BOOLEAN CaseSensitive)
1373 {
1374 PFILE_RECORD_HEADER MftRecord;
1375 PNTFS_ATTR_CONTEXT IndexRootCtx;
1376 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1377 PCHAR IndexRecord;
1378 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1379 NTSTATUS Status;
1380 ULONG CurrentEntry = 0;
1381
1382 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n",
1383 Vcb,
1384 ParentMFTIndex,
1385 FileName,
1386 DirSearch ? "TRUE" : "FALSE",
1387 NewDataSize,
1388 NewAllocationSize,
1389 CaseSensitive ? "TRUE" : "FALSE");
1390
1391 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1392 Vcb->NtfsInfo.BytesPerFileRecord,
1393 TAG_NTFS);
1394 if (MftRecord == NULL)
1395 {
1396 return STATUS_INSUFFICIENT_RESOURCES;
1397 }
1398
1399 Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
1400 if (!NT_SUCCESS(Status))
1401 {
1402 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1403 return Status;
1404 }
1405
1406 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1407 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1408 if (!NT_SUCCESS(Status))
1409 {
1410 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1411 return Status;
1412 }
1413
1414 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1415 if (IndexRecord == NULL)
1416 {
1417 ReleaseAttributeContext(IndexRootCtx);
1418 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1419 return STATUS_INSUFFICIENT_RESOURCES;
1420 }
1421
1422 Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(&IndexRootCtx->Record));
1423 if (!NT_SUCCESS(Status))
1424 {
1425 DPRINT1("ERROR: Failed to read Index Root!\n");
1426 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1427 ReleaseAttributeContext(IndexRootCtx);
1428 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1429 }
1430
1431 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1432 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1433 // Index root is always resident.
1434 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1435
1436 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1437
1438 Status = UpdateIndexEntryFileNameSize(Vcb,
1439 MftRecord,
1440 IndexRecord,
1441 IndexRoot->SizeOfEntry,
1442 IndexEntry,
1443 IndexEntryEnd,
1444 FileName,
1445 &CurrentEntry,
1446 &CurrentEntry,
1447 DirSearch,
1448 NewDataSize,
1449 NewAllocationSize,
1450 CaseSensitive);
1451
1452 if (Status == STATUS_PENDING)
1453 {
1454 // we need to write the index root attribute back to disk
1455 ULONG LengthWritten;
1456 Status = WriteAttribute(Vcb, IndexRootCtx, 0, (PUCHAR)IndexRecord, AttributeDataLength(&IndexRootCtx->Record), &LengthWritten);
1457 if (!NT_SUCCESS(Status))
1458 {
1459 DPRINT1("ERROR: Couldn't update Index Root!\n");
1460 }
1461
1462 }
1463
1464 ReleaseAttributeContext(IndexRootCtx);
1465 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1466 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1467
1468 return Status;
1469 }
1470
1471 /**
1472 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1473 * proper index entry.
1474 * (Heavily based on BrowseIndexEntries)
1475 */
1476 NTSTATUS
1477 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
1478 PFILE_RECORD_HEADER MftRecord,
1479 PCHAR IndexRecord,
1480 ULONG IndexBlockSize,
1481 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1482 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1483 PUNICODE_STRING FileName,
1484 PULONG StartEntry,
1485 PULONG CurrentEntry,
1486 BOOLEAN DirSearch,
1487 ULONGLONG NewDataSize,
1488 ULONGLONG NewAllocatedSize,
1489 BOOLEAN CaseSensitive)
1490 {
1491 NTSTATUS Status;
1492 ULONG RecordOffset;
1493 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1494 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1495 ULONGLONG IndexAllocationSize;
1496 PINDEX_BUFFER IndexBuffer;
1497
1498 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n",
1499 Vcb,
1500 MftRecord,
1501 IndexRecord,
1502 IndexBlockSize,
1503 FirstEntry,
1504 LastEntry,
1505 FileName,
1506 *StartEntry,
1507 *CurrentEntry,
1508 DirSearch ? "TRUE" : "FALSE",
1509 NewDataSize,
1510 NewAllocatedSize,
1511 CaseSensitive ? "TRUE" : "FALSE");
1512
1513 // find the index entry responsible for the file we're trying to update
1514 IndexEntry = FirstEntry;
1515 while (IndexEntry < LastEntry &&
1516 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1517 {
1518 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > NTFS_FILE_FIRST_USER_FILE &&
1519 *CurrentEntry >= *StartEntry &&
1520 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1521 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
1522 {
1523 *StartEntry = *CurrentEntry;
1524 IndexEntry->FileName.DataSize = NewDataSize;
1525 IndexEntry->FileName.AllocatedSize = NewAllocatedSize;
1526 // indicate that the caller will still need to write the structure to the disk
1527 return STATUS_PENDING;
1528 }
1529
1530 (*CurrentEntry) += 1;
1531 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1532 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1533 }
1534
1535 /* If we're already browsing a subnode */
1536 if (IndexRecord == NULL)
1537 {
1538 return STATUS_OBJECT_PATH_NOT_FOUND;
1539 }
1540
1541 /* If there's no subnode */
1542 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1543 {
1544 return STATUS_OBJECT_PATH_NOT_FOUND;
1545 }
1546
1547 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1548 if (!NT_SUCCESS(Status))
1549 {
1550 DPRINT("Corrupted filesystem!\n");
1551 return Status;
1552 }
1553
1554 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1555 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1556 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1557 {
1558 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1559 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1560 if (!NT_SUCCESS(Status))
1561 {
1562 break;
1563 }
1564
1565 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1566 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1567 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1568 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1569 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1570 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1571
1572 Status = UpdateIndexEntryFileNameSize(NULL,
1573 NULL,
1574 NULL,
1575 0,
1576 FirstEntry,
1577 LastEntry,
1578 FileName,
1579 StartEntry,
1580 CurrentEntry,
1581 DirSearch,
1582 NewDataSize,
1583 NewAllocatedSize,
1584 CaseSensitive);
1585 if (Status == STATUS_PENDING)
1586 {
1587 // write the index record back to disk
1588 ULONG Written;
1589
1590 // first we need to update the fixup values for the index block
1591 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1592 if (!NT_SUCCESS(Status))
1593 {
1594 DPRINT1("Error: Failed to update fixup sequence array!\n");
1595 break;
1596 }
1597
1598 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written);
1599 if (!NT_SUCCESS(Status))
1600 {
1601 DPRINT1("ERROR Performing write!\n");
1602 break;
1603 }
1604
1605 Status = STATUS_SUCCESS;
1606 break;
1607 }
1608 if (NT_SUCCESS(Status))
1609 {
1610 break;
1611 }
1612 }
1613
1614 ReleaseAttributeContext(IndexAllocationCtx);
1615 return Status;
1616 }
1617
1618 /**
1619 * @name UpdateFileRecord
1620 * @implemented
1621 *
1622 * Writes a file record to the master file table, at a given index.
1623 *
1624 * @param Vcb
1625 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1626 *
1627 * @param MftIndex
1628 * Target index in the master file table to store the file record.
1629 *
1630 * @param FileRecord
1631 * Pointer to the complete file record which will be written to the master file table.
1632 *
1633 * @return
1634 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1635 *
1636 */
1637 NTSTATUS
1638 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
1639 ULONGLONG MftIndex,
1640 PFILE_RECORD_HEADER FileRecord)
1641 {
1642 ULONG BytesWritten;
1643 NTSTATUS Status = STATUS_SUCCESS;
1644
1645 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord);
1646
1647 // Add the fixup array to prepare the data for writing to disk
1648 AddFixupArray(Vcb, &FileRecord->Ntfs);
1649
1650 // write the file record to the master file table
1651 Status = WriteAttribute(Vcb, Vcb->MFTContext, MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)FileRecord, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
1652
1653 if (!NT_SUCCESS(Status))
1654 {
1655 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
1656 }
1657
1658 // remove the fixup array (so the file record pointer can still be used)
1659 FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs);
1660
1661 return Status;
1662 }
1663
1664
1665 NTSTATUS
1666 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
1667 PNTFS_RECORD_HEADER Record)
1668 {
1669 USHORT *USA;
1670 USHORT USANumber;
1671 USHORT USACount;
1672 USHORT *Block;
1673
1674 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
1675 USANumber = *(USA++);
1676 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
1677 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
1678
1679 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
1680
1681 while (USACount)
1682 {
1683 if (*Block != USANumber)
1684 {
1685 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
1686 return STATUS_UNSUCCESSFUL;
1687 }
1688 *Block = *(USA++);
1689 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
1690 USACount--;
1691 }
1692
1693 return STATUS_SUCCESS;
1694 }
1695
1696 /**
1697 * @name AddNewMftEntry
1698 * @implemented
1699 *
1700 * Adds a file record to the master file table of a given device.
1701 *
1702 * @param FileRecord
1703 * Pointer to a complete file record which will be saved to disk.
1704 *
1705 * @param DeviceExt
1706 * Pointer to the DEVICE_EXTENSION of the target drive.
1707 *
1708 * @param DestinationIndex
1709 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1710 *
1711 * @param CanWait
1712 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
1713 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
1714 *
1715 * @return
1716 * STATUS_SUCCESS on success.
1717 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1718 * to read the attribute.
1719 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1720 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
1721 */
1722 NTSTATUS
1723 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
1724 PDEVICE_EXTENSION DeviceExt,
1725 PULONGLONG DestinationIndex,
1726 BOOLEAN CanWait)
1727 {
1728 NTSTATUS Status = STATUS_SUCCESS;
1729 ULONGLONG MftIndex;
1730 RTL_BITMAP Bitmap;
1731 ULONGLONG BitmapDataSize;
1732 ULONGLONG AttrBytesRead;
1733 PUCHAR BitmapData;
1734 ULONG LengthWritten;
1735 PNTFS_ATTR_CONTEXT BitmapContext;
1736 LARGE_INTEGER BitmapBits;
1737 UCHAR SystemReservedBits;
1738
1739 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
1740
1741 // First, we have to read the mft's $Bitmap attribute
1742 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
1743 if (!NT_SUCCESS(Status))
1744 {
1745 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1746 return Status;
1747 }
1748
1749 // allocate a buffer for the $Bitmap attribute
1750 BitmapDataSize = AttributeDataLength(&BitmapContext->Record);
1751 BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS);
1752 if (!BitmapData)
1753 {
1754 ReleaseAttributeContext(BitmapContext);
1755 return STATUS_INSUFFICIENT_RESOURCES;
1756 }
1757
1758 // read $Bitmap attribute
1759 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize);
1760
1761 if (AttrBytesRead == 0)
1762 {
1763 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1764 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1765 ReleaseAttributeContext(BitmapContext);
1766 return STATUS_OBJECT_NAME_NOT_FOUND;
1767 }
1768
1769 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records
1770 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk).
1771 SystemReservedBits = BitmapData[2];
1772 BitmapData[2] = 0xff;
1773
1774 // Calculate bit count
1775 BitmapBits.QuadPart = AttributeDataLength(&(DeviceExt->MFTContext->Record)) /
1776 DeviceExt->NtfsInfo.BytesPerFileRecord;
1777 if (BitmapBits.HighPart != 0)
1778 {
1779 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
1780 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1781 ReleaseAttributeContext(BitmapContext);
1782 return STATUS_NOT_IMPLEMENTED;
1783 }
1784
1785 // convert buffer into bitmap
1786 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart);
1787
1788 // set next available bit, preferrably after 23rd bit
1789 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
1790 if ((LONG)MftIndex == -1)
1791 {
1792 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
1793
1794 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1795 ReleaseAttributeContext(BitmapContext);
1796
1797 // Couldn't find a free record in the MFT, add some blank records and try again
1798 Status = IncreaseMftSize(DeviceExt, CanWait);
1799 if (!NT_SUCCESS(Status))
1800 {
1801 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
1802 return Status;
1803 }
1804
1805 return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait);
1806 }
1807
1808 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
1809
1810 // update file record with index
1811 FileRecord->MFTRecordNumber = MftIndex;
1812
1813 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1814
1815 // Restore the system reserved bits
1816 BitmapData[2] = SystemReservedBits;
1817
1818 // write the bitmap back to the MFT's $Bitmap attribute
1819 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten);
1820 if (!NT_SUCCESS(Status))
1821 {
1822 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1823 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1824 ReleaseAttributeContext(BitmapContext);
1825 return Status;
1826 }
1827
1828 // update the file record (write it to disk)
1829 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
1830
1831 if (!NT_SUCCESS(Status))
1832 {
1833 DPRINT1("ERROR: Unable to write file record!\n");
1834 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1835 ReleaseAttributeContext(BitmapContext);
1836 return Status;
1837 }
1838
1839 *DestinationIndex = MftIndex;
1840
1841 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1842 ReleaseAttributeContext(BitmapContext);
1843
1844 return Status;
1845 }
1846
1847 /**
1848 * @name NtfsAddFilenameToDirectory
1849 * @implemented
1850 *
1851 * Adds a $FILE_NAME attribute to a given directory index.
1852 *
1853 * @param DeviceExt
1854 * Points to the target disk's DEVICE_EXTENSION.
1855 *
1856 * @param DirectoryMftIndex
1857 * Mft index of the parent directory which will receive the file.
1858 *
1859 * @param FileReferenceNumber
1860 * File reference of the file to be added to the directory. This is a combination of the
1861 * Mft index and sequence number.
1862 *
1863 * @param FilenameAttribute
1864 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
1865 *
1866 * @param CaseSensitive
1867 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
1868 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
1869 *
1870 * @return
1871 * STATUS_SUCCESS on success.
1872 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
1873 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
1874 *
1875 * @remarks
1876 * WIP - Can only support a few files in a directory.
1877 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
1878 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
1879 * get both attributes added to its parent directory.
1880 */
1881 NTSTATUS
1882 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
1883 ULONGLONG DirectoryMftIndex,
1884 ULONGLONG FileReferenceNumber,
1885 PFILENAME_ATTRIBUTE FilenameAttribute,
1886 BOOLEAN CaseSensitive)
1887 {
1888 NTSTATUS Status = STATUS_SUCCESS;
1889 PFILE_RECORD_HEADER ParentFileRecord;
1890 PNTFS_ATTR_CONTEXT IndexRootContext;
1891 PINDEX_ROOT_ATTRIBUTE I30IndexRoot;
1892 ULONG IndexRootOffset;
1893 ULONGLONG I30IndexRootLength;
1894 ULONG LengthWritten;
1895 PNTFS_ATTR_RECORD DestinationAttribute;
1896 PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
1897 ULONG AttributeLength;
1898 PNTFS_ATTR_RECORD NextAttribute;
1899 PB_TREE NewTree;
1900 ULONG BtreeIndexLength;
1901 ULONG MaxIndexSize;
1902
1903 // Allocate memory for the parent directory
1904 ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool,
1905 DeviceExt->NtfsInfo.BytesPerFileRecord,
1906 TAG_NTFS);
1907 if (!ParentFileRecord)
1908 {
1909 DPRINT1("ERROR: Couldn't allocate memory for file record!\n");
1910 return STATUS_INSUFFICIENT_RESOURCES;
1911 }
1912
1913 // Open the parent directory
1914 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
1915 if (!NT_SUCCESS(Status))
1916 {
1917 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
1918 DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n",
1919 DirectoryMftIndex);
1920 return Status;
1921 }
1922
1923 DPRINT1("Dumping old parent file record:\n");
1924 NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
1925
1926 // Find the index root attribute for the directory
1927 Status = FindAttribute(DeviceExt,
1928 ParentFileRecord,
1929 AttributeIndexRoot,
1930 L"$I30",
1931 4,
1932 &IndexRootContext,
1933 &IndexRootOffset);
1934 if (!NT_SUCCESS(Status))
1935 {
1936 DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n",
1937 DirectoryMftIndex);
1938 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
1939 return Status;
1940 }
1941
1942 // Find the maximum index size given what the file record can hold
1943 MaxIndexSize = DeviceExt->NtfsInfo.BytesPerFileRecord
1944 - IndexRootOffset
1945 - IndexRootContext->Record.Resident.ValueOffset
1946 - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header)
1947 - (sizeof(ULONG) * 2);
1948
1949 // Allocate memory for the index root data
1950 I30IndexRootLength = AttributeDataLength(&IndexRootContext->Record);
1951 I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS);
1952 if (!I30IndexRoot)
1953 {
1954 DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n");
1955 ReleaseAttributeContext(IndexRootContext);
1956 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
1957 return STATUS_INSUFFICIENT_RESOURCES;
1958 }
1959
1960 // Read the Index Root
1961 Status = ReadAttribute(DeviceExt, IndexRootContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength);
1962 if (!NT_SUCCESS(Status))
1963 {
1964 DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex);
1965 ReleaseAttributeContext(IndexRootContext);
1966 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
1967 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
1968 return Status;
1969 }
1970
1971 // Convert the index to a B*Tree
1972 Status = CreateBTreeFromIndex(DeviceExt,
1973 ParentFileRecord,
1974 IndexRootContext,
1975 I30IndexRoot,
1976 &NewTree);
1977 if (!NT_SUCCESS(Status))
1978 {
1979 DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
1980 ReleaseAttributeContext(IndexRootContext);
1981 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
1982 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
1983 return Status;
1984 }
1985
1986 DumpBTree(NewTree);
1987
1988 // Insert the key for the file we're adding
1989 Status = NtfsInsertKey(FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive);
1990 if (!NT_SUCCESS(Status))
1991 {
1992 DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
1993 DestroyBTree(NewTree);
1994 ReleaseAttributeContext(IndexRootContext);
1995 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
1996 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
1997 return Status;
1998 }
1999
2000 DumpBTree(NewTree);
2001
2002 // Convert B*Tree back to Index
2003 Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
2004 if (!NT_SUCCESS(Status))
2005 {
2006 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2007 DestroyBTree(NewTree);
2008 ReleaseAttributeContext(IndexRootContext);
2009 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2010 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2011 return Status;
2012 }
2013
2014 // Create the Index Root from the B*Tree
2015 Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexSize, &NewIndexRoot, &BtreeIndexLength);
2016 if (!NT_SUCCESS(Status))
2017 {
2018 DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
2019 DestroyBTree(NewTree);
2020 ReleaseAttributeContext(IndexRootContext);
2021 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2022 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2023 return Status;
2024 }
2025
2026 // We're done with the B-Tree now
2027 DestroyBTree(NewTree);
2028
2029 // Write back the new index root attribute to the parent directory file record
2030
2031 // First, we need to resize the attribute.
2032 // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
2033 // We can't set the size as we normally would, because if we extend past the file record,
2034 // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with
2035 // $ATTRIBUTE_LIST's.
2036 AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
2037
2038 if (AttributeLength != IndexRootContext->Record.Resident.ValueLength)
2039 {
2040 DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset);
2041
2042 // Find the attribute (or attribute-end marker) after the index root
2043 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)DestinationAttribute + DestinationAttribute->Length);
2044 if (NextAttribute->Type != AttributeEnd)
2045 {
2046 DPRINT1("FIXME: For now, only resizing index root at the end of a file record is supported!\n");
2047 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2048 ReleaseAttributeContext(IndexRootContext);
2049 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2050 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2051 return STATUS_NOT_IMPLEMENTED;
2052 }
2053
2054 // Update the length of the attribute in the file record of the parent directory
2055 InternalSetResidentAttributeLength(IndexRootContext,
2056 ParentFileRecord,
2057 IndexRootOffset,
2058 AttributeLength);
2059 }
2060
2061 NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord);
2062
2063 Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2064 if (!NT_SUCCESS(Status))
2065 {
2066 DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex);
2067 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2068 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2069 ReleaseAttributeContext(IndexRootContext);
2070 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2071 return Status;
2072 }
2073
2074 // Write the new index root to disk
2075 Status = WriteAttribute(DeviceExt,
2076 IndexRootContext,
2077 0,
2078 (PUCHAR)NewIndexRoot,
2079 AttributeLength,
2080 &LengthWritten);
2081 if (!NT_SUCCESS(Status))
2082 {
2083 DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
2084 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2085 ReleaseAttributeContext(IndexRootContext);
2086 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2087 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2088 return Status;
2089 }
2090
2091 // re-read the parent file record, so we can dump it
2092 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2093 if (!NT_SUCCESS(Status))
2094 {
2095 DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n");
2096 }
2097 else
2098 {
2099 DPRINT1("Dumping new parent file record:\n");
2100 NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
2101 }
2102
2103 // Cleanup
2104 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2105 ReleaseAttributeContext(IndexRootContext);
2106 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2107 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2108
2109 return Status;
2110 }
2111
2112 NTSTATUS
2113 AddFixupArray(PDEVICE_EXTENSION Vcb,
2114 PNTFS_RECORD_HEADER Record)
2115 {
2116 USHORT *pShortToFixUp;
2117 ULONG ArrayEntryCount = Record->UsaCount - 1;
2118 ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2;
2119 ULONG i;
2120
2121 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
2122
2123 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
2124
2125 fixupArray->USN++;
2126
2127 for (i = 0; i < ArrayEntryCount; i++)
2128 {
2129 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
2130
2131 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
2132 fixupArray->Array[i] = *pShortToFixUp;
2133 *pShortToFixUp = fixupArray->USN;
2134 Offset += Vcb->NtfsInfo.BytesPerSector;
2135 }
2136
2137 return STATUS_SUCCESS;
2138 }
2139
2140 NTSTATUS
2141 ReadLCN(PDEVICE_EXTENSION Vcb,
2142 ULONGLONG lcn,
2143 ULONG count,
2144 PVOID buffer)
2145 {
2146 LARGE_INTEGER DiskSector;
2147
2148 DiskSector.QuadPart = lcn;
2149
2150 return NtfsReadSectors(Vcb->StorageDevice,
2151 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
2152 count * Vcb->NtfsInfo.SectorsPerCluster,
2153 Vcb->NtfsInfo.BytesPerSector,
2154 buffer,
2155 FALSE);
2156 }
2157
2158
2159 BOOLEAN
2160 CompareFileName(PUNICODE_STRING FileName,
2161 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
2162 BOOLEAN DirSearch,
2163 BOOLEAN CaseSensitive)
2164 {
2165 BOOLEAN Ret, Alloc = FALSE;
2166 UNICODE_STRING EntryName;
2167
2168 EntryName.Buffer = IndexEntry->FileName.Name;
2169 EntryName.Length =
2170 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
2171
2172 if (DirSearch)
2173 {
2174 UNICODE_STRING IntFileName;
2175 if (!CaseSensitive)
2176 {
2177 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
2178 Alloc = TRUE;
2179 }
2180 else
2181 {
2182 IntFileName = *FileName;
2183 }
2184
2185 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL);
2186
2187 if (Alloc)
2188 {
2189 RtlFreeUnicodeString(&IntFileName);
2190 }
2191
2192 return Ret;
2193 }
2194 else
2195 {
2196 return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0);
2197 }
2198 }
2199
2200 #if 0
2201 static
2202 VOID
2203 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
2204 {
2205 DPRINT1("Entry: %p\n", IndexEntry);
2206 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
2207 DPRINT1("\tLength: %u\n", IndexEntry->Length);
2208 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
2209 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
2210 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
2211 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
2212 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
2213 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
2214 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
2215 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
2216 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
2217 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
2218 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
2219 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
2220 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
2221 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
2222 }
2223 #endif
2224
2225 NTSTATUS
2226 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
2227 PFILE_RECORD_HEADER MftRecord,
2228 PCHAR IndexRecord,
2229 ULONG IndexBlockSize,
2230 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
2231 PINDEX_ENTRY_ATTRIBUTE LastEntry,
2232 PUNICODE_STRING FileName,
2233 PULONG StartEntry,
2234 PULONG CurrentEntry,
2235 BOOLEAN DirSearch,
2236 BOOLEAN CaseSensitive,
2237 ULONGLONG *OutMFTIndex)
2238 {
2239 NTSTATUS Status;
2240 ULONG RecordOffset;
2241 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
2242 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
2243 ULONGLONG IndexAllocationSize;
2244 PINDEX_BUFFER IndexBuffer;
2245
2246 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
2247 Vcb,
2248 MftRecord,
2249 IndexRecord,
2250 IndexBlockSize,
2251 FirstEntry,
2252 LastEntry,
2253 FileName,
2254 *StartEntry,
2255 *CurrentEntry,
2256 DirSearch ? "TRUE" : "FALSE",
2257 CaseSensitive ? "TRUE" : "FALSE",
2258 OutMFTIndex);
2259
2260 IndexEntry = FirstEntry;
2261 while (IndexEntry < LastEntry &&
2262 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
2263 {
2264 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE &&
2265 *CurrentEntry >= *StartEntry &&
2266 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
2267 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
2268 {
2269 *StartEntry = *CurrentEntry;
2270 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
2271 return STATUS_SUCCESS;
2272 }
2273
2274 (*CurrentEntry) += 1;
2275 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
2276 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
2277 }
2278
2279 /* If we're already browsing a subnode */
2280 if (IndexRecord == NULL)
2281 {
2282 return STATUS_OBJECT_PATH_NOT_FOUND;
2283 }
2284
2285 /* If there's no subnode */
2286 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
2287 {
2288 return STATUS_OBJECT_PATH_NOT_FOUND;
2289 }
2290
2291 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
2292 if (!NT_SUCCESS(Status))
2293 {
2294 DPRINT1("Corrupted filesystem!\n");
2295 return Status;
2296 }
2297
2298 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
2299 Status = STATUS_OBJECT_PATH_NOT_FOUND;
2300 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
2301 {
2302 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
2303 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
2304 if (!NT_SUCCESS(Status))
2305 {
2306 break;
2307 }
2308
2309 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
2310 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
2311 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
2312 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
2313 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
2314 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
2315
2316 Status = BrowseIndexEntries(NULL,
2317 NULL,
2318 NULL,
2319 0,
2320 FirstEntry,
2321 LastEntry,
2322 FileName,
2323 StartEntry,
2324 CurrentEntry,
2325 DirSearch,
2326 CaseSensitive,
2327 OutMFTIndex);
2328 if (NT_SUCCESS(Status))
2329 {
2330 break;
2331 }
2332 }
2333
2334 ReleaseAttributeContext(IndexAllocationCtx);
2335 return Status;
2336 }
2337
2338 NTSTATUS
2339 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
2340 ULONGLONG MFTIndex,
2341 PUNICODE_STRING FileName,
2342 PULONG FirstEntry,
2343 BOOLEAN DirSearch,
2344 BOOLEAN CaseSensitive,
2345 ULONGLONG *OutMFTIndex)
2346 {
2347 PFILE_RECORD_HEADER MftRecord;
2348 PNTFS_ATTR_CONTEXT IndexRootCtx;
2349 PINDEX_ROOT_ATTRIBUTE IndexRoot;
2350 PCHAR IndexRecord;
2351 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
2352 NTSTATUS Status;
2353 ULONG CurrentEntry = 0;
2354
2355 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n",
2356 Vcb,
2357 MFTIndex,
2358 FileName,
2359 *FirstEntry,
2360 DirSearch ? "TRUE" : "FALSE",
2361 CaseSensitive ? "TRUE" : "FALSE",
2362 OutMFTIndex);
2363
2364 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
2365 Vcb->NtfsInfo.BytesPerFileRecord,
2366 TAG_NTFS);
2367 if (MftRecord == NULL)
2368 {
2369 return STATUS_INSUFFICIENT_RESOURCES;
2370 }
2371
2372 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
2373 if (!NT_SUCCESS(Status))
2374 {
2375 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2376 return Status;
2377 }
2378
2379 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
2380 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
2381 if (!NT_SUCCESS(Status))
2382 {
2383 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2384 return Status;
2385 }
2386
2387 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
2388 if (IndexRecord == NULL)
2389 {
2390 ReleaseAttributeContext(IndexRootCtx);
2391 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2392 return STATUS_INSUFFICIENT_RESOURCES;
2393 }
2394
2395 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
2396 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
2397 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
2398 /* Index root is always resident. */
2399 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
2400 ReleaseAttributeContext(IndexRootCtx);
2401
2402 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
2403
2404 Status = BrowseIndexEntries(Vcb,
2405 MftRecord,
2406 IndexRecord,
2407 IndexRoot->SizeOfEntry,
2408 IndexEntry,
2409 IndexEntryEnd,
2410 FileName,
2411 FirstEntry,
2412 &CurrentEntry,
2413 DirSearch,
2414 CaseSensitive,
2415 OutMFTIndex);
2416
2417 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2418 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2419
2420 return Status;
2421 }
2422
2423 NTSTATUS
2424 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
2425 PUNICODE_STRING PathName,
2426 BOOLEAN CaseSensitive,
2427 PFILE_RECORD_HEADER *FileRecord,
2428 PULONGLONG MFTIndex,
2429 ULONGLONG CurrentMFTIndex)
2430 {
2431 UNICODE_STRING Current, Remaining;
2432 NTSTATUS Status;
2433 ULONG FirstEntry = 0;
2434
2435 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
2436 Vcb,
2437 PathName,
2438 CaseSensitive ? "TRUE" : "FALSE",
2439 FileRecord,
2440 MFTIndex,
2441 CurrentMFTIndex);
2442
2443 FsRtlDissectName(*PathName, &Current, &Remaining);
2444
2445 while (Current.Length != 0)
2446 {
2447 DPRINT("Current: %wZ\n", &Current);
2448
2449 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex);
2450 if (!NT_SUCCESS(Status))
2451 {
2452 return Status;
2453 }
2454
2455 if (Remaining.Length == 0)
2456 break;
2457
2458 FsRtlDissectName(Current, &Current, &Remaining);
2459 }
2460
2461 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2462 if (*FileRecord == NULL)
2463 {
2464 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
2465 return STATUS_INSUFFICIENT_RESOURCES;
2466 }
2467
2468 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2469 if (!NT_SUCCESS(Status))
2470 {
2471 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
2472 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2473 return Status;
2474 }
2475
2476 *MFTIndex = CurrentMFTIndex;
2477
2478 return STATUS_SUCCESS;
2479 }
2480
2481 NTSTATUS
2482 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
2483 PUNICODE_STRING PathName,
2484 BOOLEAN CaseSensitive,
2485 PFILE_RECORD_HEADER *FileRecord,
2486 PULONGLONG MFTIndex)
2487 {
2488 return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT);
2489 }
2490
2491 /**
2492 * @name NtfsDumpFileRecord
2493 * @implemented
2494 *
2495 * Provides diagnostic information about a file record. Prints a hex dump
2496 * of the entire record (based on the size reported by FileRecord->ByesInUse),
2497 * then prints a dump of each attribute.
2498 *
2499 * @param Vcb
2500 * Pointer to a DEVICE_EXTENSION describing the volume.
2501 *
2502 * @param FileRecord
2503 * Pointer to the file record to be analyzed.
2504 *
2505 * @remarks
2506 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
2507 * in size, and not just the header.
2508 *
2509 */
2510 VOID
2511 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
2512 PFILE_RECORD_HEADER FileRecord)
2513 {
2514 ULONG i, j;
2515
2516 // dump binary data, 8 bytes at a time
2517 for (i = 0; i < FileRecord->BytesInUse; i += 8)
2518 {
2519 // display current offset, in hex
2520 DbgPrint("\t%03x\t", i);
2521
2522 // display hex value of each of the next 8 bytes
2523 for (j = 0; j < 8; j++)
2524 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
2525 DbgPrint("\n");
2526 }
2527
2528 NtfsDumpFileAttributes(Vcb, FileRecord);
2529 }
2530
2531 NTSTATUS
2532 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
2533 PUNICODE_STRING SearchPattern,
2534 PULONG FirstEntry,
2535 PFILE_RECORD_HEADER *FileRecord,
2536 PULONGLONG MFTIndex,
2537 ULONGLONG CurrentMFTIndex,
2538 BOOLEAN CaseSensitive)
2539 {
2540 NTSTATUS Status;
2541
2542 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n",
2543 Vcb,
2544 SearchPattern,
2545 *FirstEntry,
2546 FileRecord,
2547 MFTIndex,
2548 CurrentMFTIndex,
2549 (CaseSensitive ? "TRUE" : "FALSE"));
2550
2551 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, CaseSensitive, &CurrentMFTIndex);
2552 if (!NT_SUCCESS(Status))
2553 {
2554 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
2555 return Status;
2556 }
2557
2558 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2559 if (*FileRecord == NULL)
2560 {
2561 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
2562 return STATUS_INSUFFICIENT_RESOURCES;
2563 }
2564
2565 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2566 if (!NT_SUCCESS(Status))
2567 {
2568 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
2569 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2570 return Status;
2571 }
2572
2573 *MFTIndex = CurrentMFTIndex;
2574
2575 return STATUS_SUCCESS;
2576 }
2577
2578 /* EOF */