[NTFS] - Add a few fixes and improvements, most from CR-123:
[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 ExFreePoolWithTag(AttribData, TAG_NTFS);
754 _SEH2_YIELD(return _SEH2_GetExceptionCode());
755 } _SEH2_END;
756
757 // Mark the attribute as non-resident (we wait until after we know the LargeMcb was initialized)
758 AttrContext->Record.IsNonResident = Destination->IsNonResident = 1;
759
760 // Update file record on disk
761 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
762 if (!NT_SUCCESS(Status))
763 {
764 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
765 if (AttribDataSize.QuadPart > 0)
766 ExFreePoolWithTag(AttribData, TAG_NTFS);
767 return Status;
768 }
769
770 // Now we can treat the attribute as non-resident and enlarge it normally
771 Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize);
772 if (!NT_SUCCESS(Status))
773 {
774 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
775 if (AttribDataSize.QuadPart > 0)
776 ExFreePoolWithTag(AttribData, TAG_NTFS);
777 return Status;
778 }
779
780 // restore the back-up attribute, if we made one
781 if (AttribDataSize.QuadPart > 0)
782 {
783 Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
784 if (!NT_SUCCESS(Status))
785 {
786 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
787 // TODO: Reverse migration so no data is lost
788 ExFreePoolWithTag(AttribData, TAG_NTFS);
789 return Status;
790 }
791
792 ExFreePoolWithTag(AttribData, TAG_NTFS);
793 }
794 }
795 }
796 }
797 else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength)
798 {
799 // we need to decrease the length
800 if (NextAttribute->Type != AttributeEnd)
801 {
802 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
803 return STATUS_NOT_IMPLEMENTED;
804 }
805 }
806
807 // set the new length of the resident attribute (if we didn't migrate it)
808 if (!AttrContext->Record.IsNonResident)
809 InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
810
811 return STATUS_SUCCESS;
812 }
813
814 ULONG
815 ReadAttribute(PDEVICE_EXTENSION Vcb,
816 PNTFS_ATTR_CONTEXT Context,
817 ULONGLONG Offset,
818 PCHAR Buffer,
819 ULONG Length)
820 {
821 ULONGLONG LastLCN;
822 PUCHAR DataRun;
823 LONGLONG DataRunOffset;
824 ULONGLONG DataRunLength;
825 LONGLONG DataRunStartLCN;
826 ULONGLONG CurrentOffset;
827 ULONG ReadLength;
828 ULONG AlreadyRead;
829 NTSTATUS Status;
830
831 //TEMPTEMP
832 PUCHAR TempBuffer;
833
834 if (!Context->Record.IsNonResident)
835 {
836 if (Offset > Context->Record.Resident.ValueLength)
837 return 0;
838 if (Offset + Length > Context->Record.Resident.ValueLength)
839 Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
840 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
841 return Length;
842 }
843
844 /*
845 * Non-resident attribute
846 */
847
848 /*
849 * I. Find the corresponding start data run.
850 */
851
852 AlreadyRead = 0;
853
854 // FIXME: Cache seems to be non-working. Disable it for now
855 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
856 if (0)
857 {
858 DataRun = Context->CacheRun;
859 LastLCN = Context->CacheRunLastLCN;
860 DataRunStartLCN = Context->CacheRunStartLCN;
861 DataRunLength = Context->CacheRunLength;
862 CurrentOffset = Context->CacheRunCurrentOffset;
863 }
864 else
865 {
866 //TEMPTEMP
867 ULONG UsedBufferSize;
868 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
869
870 LastLCN = 0;
871 CurrentOffset = 0;
872
873 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
874 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
875 TempBuffer,
876 Vcb->NtfsInfo.BytesPerFileRecord,
877 &UsedBufferSize);
878
879 DataRun = TempBuffer;
880
881 while (1)
882 {
883 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
884 if (DataRunOffset != -1)
885 {
886 /* Normal data run. */
887 DataRunStartLCN = LastLCN + DataRunOffset;
888 LastLCN = DataRunStartLCN;
889 }
890 else
891 {
892 /* Sparse data run. */
893 DataRunStartLCN = -1;
894 }
895
896 if (Offset >= CurrentOffset &&
897 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
898 {
899 break;
900 }
901
902 if (*DataRun == 0)
903 {
904 return AlreadyRead;
905 }
906
907 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
908 }
909 }
910
911 /*
912 * II. Go through the run list and read the data
913 */
914
915 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
916 if (DataRunStartLCN == -1)
917 {
918 RtlZeroMemory(Buffer, ReadLength);
919 Status = STATUS_SUCCESS;
920 }
921 else
922 {
923 Status = NtfsReadDisk(Vcb->StorageDevice,
924 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
925 ReadLength,
926 Vcb->NtfsInfo.BytesPerSector,
927 (PVOID)Buffer,
928 FALSE);
929 }
930 if (NT_SUCCESS(Status))
931 {
932 Length -= ReadLength;
933 Buffer += ReadLength;
934 AlreadyRead += ReadLength;
935
936 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
937 {
938 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
939 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
940 if (DataRunOffset != (ULONGLONG)-1)
941 {
942 DataRunStartLCN = LastLCN + DataRunOffset;
943 LastLCN = DataRunStartLCN;
944 }
945 else
946 DataRunStartLCN = -1;
947 }
948
949 while (Length > 0)
950 {
951 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
952 if (DataRunStartLCN == -1)
953 RtlZeroMemory(Buffer, ReadLength);
954 else
955 {
956 Status = NtfsReadDisk(Vcb->StorageDevice,
957 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
958 ReadLength,
959 Vcb->NtfsInfo.BytesPerSector,
960 (PVOID)Buffer,
961 FALSE);
962 if (!NT_SUCCESS(Status))
963 break;
964 }
965
966 Length -= ReadLength;
967 Buffer += ReadLength;
968 AlreadyRead += ReadLength;
969
970 /* We finished this request, but there still data in this data run. */
971 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
972 break;
973
974 /*
975 * Go to next run in the list.
976 */
977
978 if (*DataRun == 0)
979 break;
980 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
981 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
982 if (DataRunOffset != -1)
983 {
984 /* Normal data run. */
985 DataRunStartLCN = LastLCN + DataRunOffset;
986 LastLCN = DataRunStartLCN;
987 }
988 else
989 {
990 /* Sparse data run. */
991 DataRunStartLCN = -1;
992 }
993 } /* while */
994
995 } /* if Disk */
996
997 // TEMPTEMP
998 if (Context->Record.IsNonResident)
999 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1000
1001 Context->CacheRun = DataRun;
1002 Context->CacheRunOffset = Offset + AlreadyRead;
1003 Context->CacheRunStartLCN = DataRunStartLCN;
1004 Context->CacheRunLength = DataRunLength;
1005 Context->CacheRunLastLCN = LastLCN;
1006 Context->CacheRunCurrentOffset = CurrentOffset;
1007
1008 return AlreadyRead;
1009 }
1010
1011
1012 /**
1013 * @name WriteAttribute
1014 * @implemented
1015 *
1016 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1017 * and it still needs more documentation / cleaning up.
1018 *
1019 * @param Vcb
1020 * Volume Control Block indicating which volume to write the attribute to
1021 *
1022 * @param Context
1023 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1024 *
1025 * @param Offset
1026 * Offset, in bytes, from the beginning of the attribute indicating where to start
1027 * writing data
1028 *
1029 * @param Buffer
1030 * The data that's being written to the device
1031 *
1032 * @param Length
1033 * How much data will be written, in bytes
1034 *
1035 * @param RealLengthWritten
1036 * Pointer to a ULONG which will receive how much data was written, in bytes
1037 *
1038 * @return
1039 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1040 * writing to a sparse file.
1041 *
1042 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1043 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1044 *
1045 */
1046
1047 NTSTATUS
1048 WriteAttribute(PDEVICE_EXTENSION Vcb,
1049 PNTFS_ATTR_CONTEXT Context,
1050 ULONGLONG Offset,
1051 const PUCHAR Buffer,
1052 ULONG Length,
1053 PULONG RealLengthWritten)
1054 {
1055 ULONGLONG LastLCN;
1056 PUCHAR DataRun;
1057 LONGLONG DataRunOffset;
1058 ULONGLONG DataRunLength;
1059 LONGLONG DataRunStartLCN;
1060 ULONGLONG CurrentOffset;
1061 ULONG WriteLength;
1062 NTSTATUS Status;
1063 PUCHAR SourceBuffer = Buffer;
1064 LONGLONG StartingOffset;
1065
1066 //TEMPTEMP
1067 PUCHAR TempBuffer;
1068
1069
1070 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten);
1071
1072 *RealLengthWritten = 0;
1073
1074 // is this a resident attribute?
1075 if (!Context->Record.IsNonResident)
1076 {
1077 ULONG AttributeOffset;
1078 PNTFS_ATTR_CONTEXT FoundContext;
1079 PFILE_RECORD_HEADER FileRecord;
1080
1081 if (Offset + Length > Context->Record.Resident.ValueLength)
1082 {
1083 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1084 return STATUS_INVALID_PARAMETER;
1085 }
1086
1087 FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1088
1089 if (!FileRecord)
1090 {
1091 DPRINT1("Error: Couldn't allocate file record!\n");
1092 return STATUS_NO_MEMORY;
1093 }
1094
1095 // read the file record
1096 ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1097
1098 // find where to write the attribute data to
1099 Status = FindAttribute(Vcb, FileRecord,
1100 Context->Record.Type,
1101 (PCWSTR)((PCHAR)&Context->Record + Context->Record.NameOffset),
1102 Context->Record.NameLength,
1103 &FoundContext,
1104 &AttributeOffset);
1105
1106 if (!NT_SUCCESS(Status))
1107 {
1108 DPRINT1("ERROR: Couldn't find matching attribute!\n");
1109 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1110 return Status;
1111 }
1112
1113 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->Record.Resident.ValueLength);
1114 Offset += AttributeOffset + Context->Record.Resident.ValueOffset;
1115
1116 if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord)
1117 {
1118 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1119 ReleaseAttributeContext(FoundContext);
1120 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1121 return STATUS_INVALID_PARAMETER;
1122 }
1123
1124 // copy the data being written into the file record
1125 RtlCopyMemory((PCHAR)FileRecord + Offset, Buffer, Length);
1126
1127 Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1128
1129 ReleaseAttributeContext(FoundContext);
1130 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1131
1132 if (NT_SUCCESS(Status))
1133 *RealLengthWritten = Length;
1134
1135 return Status;
1136 }
1137
1138 // This is a non-resident attribute.
1139
1140 // I. Find the corresponding start data run.
1141
1142 // FIXME: Cache seems to be non-working. Disable it for now
1143 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1144 /*if (0)
1145 {
1146 DataRun = Context->CacheRun;
1147 LastLCN = Context->CacheRunLastLCN;
1148 DataRunStartLCN = Context->CacheRunStartLCN;
1149 DataRunLength = Context->CacheRunLength;
1150 CurrentOffset = Context->CacheRunCurrentOffset;
1151 }
1152 else*/
1153 {
1154 ULONG UsedBufferSize;
1155 LastLCN = 0;
1156 CurrentOffset = 0;
1157
1158 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1159 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1160
1161 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
1162 TempBuffer,
1163 Vcb->NtfsInfo.BytesPerFileRecord,
1164 &UsedBufferSize);
1165
1166 DataRun = TempBuffer;
1167
1168 while (1)
1169 {
1170 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1171 if (DataRunOffset != -1)
1172 {
1173 // Normal data run.
1174 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1175 DataRunStartLCN = LastLCN + DataRunOffset;
1176 LastLCN = DataRunStartLCN;
1177 }
1178 else
1179 {
1180 // Sparse data run. We can't support writing to sparse files yet
1181 // (it may require increasing the allocation size).
1182 DataRunStartLCN = -1;
1183 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1184 return STATUS_NOT_IMPLEMENTED;
1185 }
1186
1187 // Have we reached the data run we're trying to write to?
1188 if (Offset >= CurrentOffset &&
1189 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
1190 {
1191 break;
1192 }
1193
1194 if (*DataRun == 0)
1195 {
1196 // We reached the last assigned cluster
1197 // TODO: assign new clusters to the end of the file.
1198 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1199 // [We can reach here by creating a new file record when the MFT isn't large enough]
1200 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1201 return STATUS_END_OF_FILE;
1202 }
1203
1204 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1205 }
1206 }
1207
1208 // II. Go through the run list and write the data
1209
1210 /* REVIEWME -- As adapted from NtfsReadAttribute():
1211 We seem to be making a special case for the first applicable data run, but I'm not sure why.
1212 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1213
1214 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
1215
1216 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
1217
1218 // Write the data to the disk
1219 Status = NtfsWriteDisk(Vcb->StorageDevice,
1220 StartingOffset,
1221 WriteLength,
1222 Vcb->NtfsInfo.BytesPerSector,
1223 (PVOID)SourceBuffer);
1224
1225 // Did the write fail?
1226 if (!NT_SUCCESS(Status))
1227 {
1228 Context->CacheRun = DataRun;
1229 Context->CacheRunOffset = Offset;
1230 Context->CacheRunStartLCN = DataRunStartLCN;
1231 Context->CacheRunLength = DataRunLength;
1232 Context->CacheRunLastLCN = LastLCN;
1233 Context->CacheRunCurrentOffset = CurrentOffset;
1234
1235 return Status;
1236 }
1237
1238 Length -= WriteLength;
1239 SourceBuffer += WriteLength;
1240 *RealLengthWritten += WriteLength;
1241
1242 // Did we write to the end of the data run?
1243 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
1244 {
1245 // Advance to the next data run
1246 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1247 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1248
1249 if (DataRunOffset != (ULONGLONG)-1)
1250 {
1251 DataRunStartLCN = LastLCN + DataRunOffset;
1252 LastLCN = DataRunStartLCN;
1253 }
1254 else
1255 DataRunStartLCN = -1;
1256 }
1257
1258 // Do we have more data to write?
1259 while (Length > 0)
1260 {
1261 // Make sure we don't write past the end of the current data run
1262 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
1263
1264 // Are we dealing with a sparse data run?
1265 if (DataRunStartLCN == -1)
1266 {
1267 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1268 return STATUS_NOT_IMPLEMENTED;
1269 }
1270 else
1271 {
1272 // write the data to the disk
1273 Status = NtfsWriteDisk(Vcb->StorageDevice,
1274 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
1275 WriteLength,
1276 Vcb->NtfsInfo.BytesPerSector,
1277 (PVOID)SourceBuffer);
1278 if (!NT_SUCCESS(Status))
1279 break;
1280 }
1281
1282 Length -= WriteLength;
1283 SourceBuffer += WriteLength;
1284 *RealLengthWritten += WriteLength;
1285
1286 // We finished this request, but there's still data in this data run.
1287 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1288 break;
1289
1290 // Go to next run in the list.
1291
1292 if (*DataRun == 0)
1293 {
1294 // that was the last run
1295 if (Length > 0)
1296 {
1297 // Failed sanity check.
1298 DPRINT1("Encountered EOF before expected!\n");
1299 return STATUS_END_OF_FILE;
1300 }
1301
1302 break;
1303 }
1304
1305 // Advance to the next data run
1306 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1307 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1308 if (DataRunOffset != -1)
1309 {
1310 // Normal data run.
1311 DataRunStartLCN = LastLCN + DataRunOffset;
1312 LastLCN = DataRunStartLCN;
1313 }
1314 else
1315 {
1316 // Sparse data run.
1317 DataRunStartLCN = -1;
1318 }
1319 } // end while (Length > 0) [more data to write]
1320
1321 // TEMPTEMP
1322 if(Context->Record.IsNonResident)
1323 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1324
1325 Context->CacheRun = DataRun;
1326 Context->CacheRunOffset = Offset + *RealLengthWritten;
1327 Context->CacheRunStartLCN = DataRunStartLCN;
1328 Context->CacheRunLength = DataRunLength;
1329 Context->CacheRunLastLCN = LastLCN;
1330 Context->CacheRunCurrentOffset = CurrentOffset;
1331
1332 return Status;
1333 }
1334
1335 NTSTATUS
1336 ReadFileRecord(PDEVICE_EXTENSION Vcb,
1337 ULONGLONG index,
1338 PFILE_RECORD_HEADER file)
1339 {
1340 ULONGLONG BytesRead;
1341
1342 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
1343
1344 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
1345 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
1346 {
1347 DPRINT1("ReadFileRecord failed: %I64u read, %lu expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
1348 return STATUS_PARTIAL_COPY;
1349 }
1350
1351 /* Apply update sequence array fixups. */
1352 DPRINT("Sequence number: %u\n", file->SequenceNumber);
1353 return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
1354 }
1355
1356
1357 /**
1358 * Searches a file's parent directory (given the parent's index in the mft)
1359 * for the given file. Upon finding an index entry for that file, updates
1360 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1361 *
1362 * (Most of this code was copied from NtfsFindMftRecord)
1363 */
1364 NTSTATUS
1365 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
1366 ULONGLONG ParentMFTIndex,
1367 PUNICODE_STRING FileName,
1368 BOOLEAN DirSearch,
1369 ULONGLONG NewDataSize,
1370 ULONGLONG NewAllocationSize,
1371 BOOLEAN CaseSensitive)
1372 {
1373 PFILE_RECORD_HEADER MftRecord;
1374 PNTFS_ATTR_CONTEXT IndexRootCtx;
1375 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1376 PCHAR IndexRecord;
1377 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1378 NTSTATUS Status;
1379 ULONG CurrentEntry = 0;
1380
1381 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %s, %I64u, %I64u, %s)\n",
1382 Vcb,
1383 ParentMFTIndex,
1384 FileName,
1385 DirSearch ? "TRUE" : "FALSE",
1386 NewDataSize,
1387 NewAllocationSize,
1388 CaseSensitive ? "TRUE" : "FALSE");
1389
1390 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1391 Vcb->NtfsInfo.BytesPerFileRecord,
1392 TAG_NTFS);
1393 if (MftRecord == NULL)
1394 {
1395 return STATUS_INSUFFICIENT_RESOURCES;
1396 }
1397
1398 Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
1399 if (!NT_SUCCESS(Status))
1400 {
1401 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1402 return Status;
1403 }
1404
1405 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1406 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1407 if (!NT_SUCCESS(Status))
1408 {
1409 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1410 return Status;
1411 }
1412
1413 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1414 if (IndexRecord == NULL)
1415 {
1416 ReleaseAttributeContext(IndexRootCtx);
1417 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1418 return STATUS_INSUFFICIENT_RESOURCES;
1419 }
1420
1421 Status = ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(&IndexRootCtx->Record));
1422 if (!NT_SUCCESS(Status))
1423 {
1424 DPRINT1("ERROR: Failed to read Index Root!\n");
1425 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1426 ReleaseAttributeContext(IndexRootCtx);
1427 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1428 }
1429
1430 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1431 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1432 // Index root is always resident.
1433 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1434
1435 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1436
1437 Status = UpdateIndexEntryFileNameSize(Vcb,
1438 MftRecord,
1439 IndexRecord,
1440 IndexRoot->SizeOfEntry,
1441 IndexEntry,
1442 IndexEntryEnd,
1443 FileName,
1444 &CurrentEntry,
1445 &CurrentEntry,
1446 DirSearch,
1447 NewDataSize,
1448 NewAllocationSize,
1449 CaseSensitive);
1450
1451 if (Status == STATUS_PENDING)
1452 {
1453 // we need to write the index root attribute back to disk
1454 ULONG LengthWritten;
1455 Status = WriteAttribute(Vcb, IndexRootCtx, 0, IndexRecord, AttributeDataLength(&IndexRootCtx->Record), &LengthWritten);
1456 if (!NT_SUCCESS(Status))
1457 {
1458 DPRINT1("ERROR: Couldn't update Index Root!\n");
1459 }
1460
1461 }
1462
1463 ReleaseAttributeContext(IndexRootCtx);
1464 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1465 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1466
1467 return Status;
1468 }
1469
1470 /**
1471 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1472 * proper index entry.
1473 * (Heavily based on BrowseIndexEntries)
1474 */
1475 NTSTATUS
1476 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
1477 PFILE_RECORD_HEADER MftRecord,
1478 PCHAR IndexRecord,
1479 ULONG IndexBlockSize,
1480 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1481 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1482 PUNICODE_STRING FileName,
1483 PULONG StartEntry,
1484 PULONG CurrentEntry,
1485 BOOLEAN DirSearch,
1486 ULONGLONG NewDataSize,
1487 ULONGLONG NewAllocatedSize,
1488 BOOLEAN CaseSensitive)
1489 {
1490 NTSTATUS Status;
1491 ULONG RecordOffset;
1492 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1493 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1494 ULONGLONG IndexAllocationSize;
1495 PINDEX_BUFFER IndexBuffer;
1496
1497 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %I64u, %I64u, %s)\n",
1498 Vcb,
1499 MftRecord,
1500 IndexRecord,
1501 IndexBlockSize,
1502 FirstEntry,
1503 LastEntry,
1504 FileName,
1505 *StartEntry,
1506 *CurrentEntry,
1507 DirSearch ? "TRUE" : "FALSE",
1508 NewDataSize,
1509 NewAllocatedSize,
1510 CaseSensitive ? "TRUE" : "FALSE");
1511
1512 // find the index entry responsible for the file we're trying to update
1513 IndexEntry = FirstEntry;
1514 while (IndexEntry < LastEntry &&
1515 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1516 {
1517 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
1518 *CurrentEntry >= *StartEntry &&
1519 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1520 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
1521 {
1522 *StartEntry = *CurrentEntry;
1523 IndexEntry->FileName.DataSize = NewDataSize;
1524 IndexEntry->FileName.AllocatedSize = NewAllocatedSize;
1525 // indicate that the caller will still need to write the structure to the disk
1526 return STATUS_PENDING;
1527 }
1528
1529 (*CurrentEntry) += 1;
1530 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1531 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1532 }
1533
1534 /* If we're already browsing a subnode */
1535 if (IndexRecord == NULL)
1536 {
1537 return STATUS_OBJECT_PATH_NOT_FOUND;
1538 }
1539
1540 /* If there's no subnode */
1541 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1542 {
1543 return STATUS_OBJECT_PATH_NOT_FOUND;
1544 }
1545
1546 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1547 if (!NT_SUCCESS(Status))
1548 {
1549 DPRINT("Corrupted filesystem!\n");
1550 return Status;
1551 }
1552
1553 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1554 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1555 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1556 {
1557 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1558 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1559 if (!NT_SUCCESS(Status))
1560 {
1561 break;
1562 }
1563
1564 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1565 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1566 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1567 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1568 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1569 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1570
1571 Status = UpdateIndexEntryFileNameSize(NULL,
1572 NULL,
1573 NULL,
1574 0,
1575 FirstEntry,
1576 LastEntry,
1577 FileName,
1578 StartEntry,
1579 CurrentEntry,
1580 DirSearch,
1581 NewDataSize,
1582 NewAllocatedSize,
1583 CaseSensitive);
1584 if (Status == STATUS_PENDING)
1585 {
1586 // write the index record back to disk
1587 ULONG Written;
1588
1589 // first we need to update the fixup values for the index block
1590 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1591 if (!NT_SUCCESS(Status))
1592 {
1593 DPRINT1("Error: Failed to update fixup sequence array!\n");
1594 break;
1595 }
1596
1597 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written);
1598 if (!NT_SUCCESS(Status))
1599 {
1600 DPRINT1("ERROR Performing write!\n");
1601 break;
1602 }
1603
1604 Status = STATUS_SUCCESS;
1605 break;
1606 }
1607 if (NT_SUCCESS(Status))
1608 {
1609 break;
1610 }
1611 }
1612
1613 ReleaseAttributeContext(IndexAllocationCtx);
1614 return Status;
1615 }
1616
1617 /**
1618 * @name UpdateFileRecord
1619 * @implemented
1620 *
1621 * Writes a file record to the master file table, at a given index.
1622 *
1623 * @param Vcb
1624 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1625 *
1626 * @param MftIndex
1627 * Target index in the master file table to store the file record.
1628 *
1629 * @param FileRecord
1630 * Pointer to the complete file record which will be written to the master file table.
1631 *
1632 * @return
1633 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1634 *
1635 */
1636 NTSTATUS
1637 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
1638 ULONGLONG MftIndex,
1639 PFILE_RECORD_HEADER FileRecord)
1640 {
1641 ULONG BytesWritten;
1642 NTSTATUS Status = STATUS_SUCCESS;
1643
1644 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord);
1645
1646 // Add the fixup array to prepare the data for writing to disk
1647 AddFixupArray(Vcb, &FileRecord->Ntfs);
1648
1649 // write the file record to the master file table
1650 Status = WriteAttribute(Vcb, Vcb->MFTContext, MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)FileRecord, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
1651
1652 if (!NT_SUCCESS(Status))
1653 {
1654 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
1655 }
1656
1657 // remove the fixup array (so the file record pointer can still be used)
1658 FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs);
1659
1660 return Status;
1661 }
1662
1663
1664 NTSTATUS
1665 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
1666 PNTFS_RECORD_HEADER Record)
1667 {
1668 USHORT *USA;
1669 USHORT USANumber;
1670 USHORT USACount;
1671 USHORT *Block;
1672
1673 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
1674 USANumber = *(USA++);
1675 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
1676 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
1677
1678 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
1679
1680 while (USACount)
1681 {
1682 if (*Block != USANumber)
1683 {
1684 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
1685 return STATUS_UNSUCCESSFUL;
1686 }
1687 *Block = *(USA++);
1688 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
1689 USACount--;
1690 }
1691
1692 return STATUS_SUCCESS;
1693 }
1694
1695 /**
1696 * @name AddNewMftEntry
1697 * @implemented
1698 *
1699 * Adds a file record to the master file table of a given device.
1700 *
1701 * @param FileRecord
1702 * Pointer to a complete file record which will be saved to disk.
1703 *
1704 * @param DeviceExt
1705 * Pointer to the DEVICE_EXTENSION of the target drive.
1706 *
1707 * @param DestinationIndex
1708 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1709 *
1710 * @param CanWait
1711 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
1712 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
1713 *
1714 * @return
1715 * STATUS_SUCCESS on success.
1716 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1717 * to read the attribute.
1718 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1719 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
1720 */
1721 NTSTATUS
1722 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
1723 PDEVICE_EXTENSION DeviceExt,
1724 PULONGLONG DestinationIndex,
1725 BOOLEAN CanWait)
1726 {
1727 NTSTATUS Status = STATUS_SUCCESS;
1728 ULONGLONG MftIndex;
1729 RTL_BITMAP Bitmap;
1730 ULONGLONG BitmapDataSize;
1731 ULONGLONG AttrBytesRead;
1732 PUCHAR BitmapData;
1733 ULONG LengthWritten;
1734 PNTFS_ATTR_CONTEXT BitmapContext;
1735 LARGE_INTEGER BitmapBits;
1736 UCHAR SystemReservedBits;
1737
1738 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
1739
1740 // First, we have to read the mft's $Bitmap attribute
1741 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
1742 if (!NT_SUCCESS(Status))
1743 {
1744 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1745 return Status;
1746 }
1747
1748 // allocate a buffer for the $Bitmap attribute
1749 BitmapDataSize = AttributeDataLength(&BitmapContext->Record);
1750 BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS);
1751 if (!BitmapData)
1752 {
1753 ReleaseAttributeContext(BitmapContext);
1754 return STATUS_INSUFFICIENT_RESOURCES;
1755 }
1756
1757 // read $Bitmap attribute
1758 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize);
1759
1760 if (AttrBytesRead == 0)
1761 {
1762 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1763 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1764 ReleaseAttributeContext(BitmapContext);
1765 return STATUS_OBJECT_NAME_NOT_FOUND;
1766 }
1767
1768 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records
1769 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk).
1770 SystemReservedBits = BitmapData[2];
1771 BitmapData[2] = 0xff;
1772
1773 // Calculate bit count
1774 BitmapBits.QuadPart = AttributeDataLength(&(DeviceExt->MFTContext->Record)) /
1775 DeviceExt->NtfsInfo.BytesPerFileRecord;
1776 if (BitmapBits.HighPart != 0)
1777 {
1778 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
1779 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1780 ReleaseAttributeContext(BitmapContext);
1781 return STATUS_NOT_IMPLEMENTED;
1782 }
1783
1784 // convert buffer into bitmap
1785 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart);
1786
1787 // set next available bit, preferrably after 23rd bit
1788 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
1789 if ((LONG)MftIndex == -1)
1790 {
1791 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
1792
1793 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1794 ReleaseAttributeContext(BitmapContext);
1795
1796 // Couldn't find a free record in the MFT, add some blank records and try again
1797 Status = IncreaseMftSize(DeviceExt, CanWait);
1798 if (!NT_SUCCESS(Status))
1799 {
1800 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
1801 return Status;
1802 }
1803
1804 return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait);
1805 }
1806
1807 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
1808
1809 // update file record with index
1810 FileRecord->MFTRecordNumber = MftIndex;
1811
1812 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1813
1814 // Restore the system reserved bits
1815 BitmapData[2] = SystemReservedBits;
1816
1817 // write the bitmap back to the MFT's $Bitmap attribute
1818 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten);
1819 if (!NT_SUCCESS(Status))
1820 {
1821 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1822 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1823 ReleaseAttributeContext(BitmapContext);
1824 return Status;
1825 }
1826
1827 // update the file record (write it to disk)
1828 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
1829
1830 if (!NT_SUCCESS(Status))
1831 {
1832 DPRINT1("ERROR: Unable to write file record!\n");
1833 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1834 ReleaseAttributeContext(BitmapContext);
1835 return Status;
1836 }
1837
1838 *DestinationIndex = MftIndex;
1839
1840 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1841 ReleaseAttributeContext(BitmapContext);
1842
1843 return Status;
1844 }
1845
1846 NTSTATUS
1847 AddFixupArray(PDEVICE_EXTENSION Vcb,
1848 PNTFS_RECORD_HEADER Record)
1849 {
1850 USHORT *pShortToFixUp;
1851 ULONG ArrayEntryCount = Record->UsaCount - 1;
1852 ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2;
1853 ULONG i;
1854
1855 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
1856
1857 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
1858
1859 fixupArray->USN++;
1860
1861 for (i = 0; i < ArrayEntryCount; i++)
1862 {
1863 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
1864
1865 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
1866 fixupArray->Array[i] = *pShortToFixUp;
1867 *pShortToFixUp = fixupArray->USN;
1868 Offset += Vcb->NtfsInfo.BytesPerSector;
1869 }
1870
1871 return STATUS_SUCCESS;
1872 }
1873
1874 NTSTATUS
1875 ReadLCN(PDEVICE_EXTENSION Vcb,
1876 ULONGLONG lcn,
1877 ULONG count,
1878 PVOID buffer)
1879 {
1880 LARGE_INTEGER DiskSector;
1881
1882 DiskSector.QuadPart = lcn;
1883
1884 return NtfsReadSectors(Vcb->StorageDevice,
1885 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
1886 count * Vcb->NtfsInfo.SectorsPerCluster,
1887 Vcb->NtfsInfo.BytesPerSector,
1888 buffer,
1889 FALSE);
1890 }
1891
1892
1893 BOOLEAN
1894 CompareFileName(PUNICODE_STRING FileName,
1895 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
1896 BOOLEAN DirSearch,
1897 BOOLEAN CaseSensitive)
1898 {
1899 BOOLEAN Ret, Alloc = FALSE;
1900 UNICODE_STRING EntryName;
1901
1902 EntryName.Buffer = IndexEntry->FileName.Name;
1903 EntryName.Length =
1904 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
1905
1906 if (DirSearch)
1907 {
1908 UNICODE_STRING IntFileName;
1909 if (!CaseSensitive)
1910 {
1911 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
1912 Alloc = TRUE;
1913 }
1914 else
1915 {
1916 IntFileName = *FileName;
1917 }
1918
1919 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL);
1920
1921 if (Alloc)
1922 {
1923 RtlFreeUnicodeString(&IntFileName);
1924 }
1925
1926 return Ret;
1927 }
1928 else
1929 {
1930 return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0);
1931 }
1932 }
1933
1934 #if 0
1935 static
1936 VOID
1937 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
1938 {
1939 DPRINT1("Entry: %p\n", IndexEntry);
1940 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
1941 DPRINT1("\tLength: %u\n", IndexEntry->Length);
1942 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
1943 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
1944 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
1945 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
1946 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
1947 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
1948 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
1949 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
1950 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
1951 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
1952 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
1953 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
1954 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
1955 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
1956 }
1957 #endif
1958
1959 NTSTATUS
1960 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
1961 PFILE_RECORD_HEADER MftRecord,
1962 PCHAR IndexRecord,
1963 ULONG IndexBlockSize,
1964 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1965 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1966 PUNICODE_STRING FileName,
1967 PULONG StartEntry,
1968 PULONG CurrentEntry,
1969 BOOLEAN DirSearch,
1970 BOOLEAN CaseSensitive,
1971 ULONGLONG *OutMFTIndex)
1972 {
1973 NTSTATUS Status;
1974 ULONG RecordOffset;
1975 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1976 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1977 ULONGLONG IndexAllocationSize;
1978 PINDEX_BUFFER IndexBuffer;
1979
1980 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
1981 Vcb,
1982 MftRecord,
1983 IndexRecord,
1984 IndexBlockSize,
1985 FirstEntry,
1986 LastEntry,
1987 FileName,
1988 *StartEntry,
1989 *CurrentEntry,
1990 DirSearch ? "TRUE" : "FALSE",
1991 CaseSensitive ? "TRUE" : "FALSE",
1992 OutMFTIndex);
1993
1994 IndexEntry = FirstEntry;
1995 while (IndexEntry < LastEntry &&
1996 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1997 {
1998 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= 0x10 &&
1999 *CurrentEntry >= *StartEntry &&
2000 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
2001 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
2002 {
2003 *StartEntry = *CurrentEntry;
2004 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
2005 return STATUS_SUCCESS;
2006 }
2007
2008 (*CurrentEntry) += 1;
2009 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
2010 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
2011 }
2012
2013 /* If we're already browsing a subnode */
2014 if (IndexRecord == NULL)
2015 {
2016 return STATUS_OBJECT_PATH_NOT_FOUND;
2017 }
2018
2019 /* If there's no subnode */
2020 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
2021 {
2022 return STATUS_OBJECT_PATH_NOT_FOUND;
2023 }
2024
2025 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
2026 if (!NT_SUCCESS(Status))
2027 {
2028 DPRINT1("Corrupted filesystem!\n");
2029 return Status;
2030 }
2031
2032 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
2033 Status = STATUS_OBJECT_PATH_NOT_FOUND;
2034 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
2035 {
2036 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
2037 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
2038 if (!NT_SUCCESS(Status))
2039 {
2040 break;
2041 }
2042
2043 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
2044 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
2045 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
2046 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
2047 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
2048 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
2049
2050 Status = BrowseIndexEntries(NULL,
2051 NULL,
2052 NULL,
2053 0,
2054 FirstEntry,
2055 LastEntry,
2056 FileName,
2057 StartEntry,
2058 CurrentEntry,
2059 DirSearch,
2060 CaseSensitive,
2061 OutMFTIndex);
2062 if (NT_SUCCESS(Status))
2063 {
2064 break;
2065 }
2066 }
2067
2068 ReleaseAttributeContext(IndexAllocationCtx);
2069 return Status;
2070 }
2071
2072 NTSTATUS
2073 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
2074 ULONGLONG MFTIndex,
2075 PUNICODE_STRING FileName,
2076 PULONG FirstEntry,
2077 BOOLEAN DirSearch,
2078 BOOLEAN CaseSensitive,
2079 ULONGLONG *OutMFTIndex)
2080 {
2081 PFILE_RECORD_HEADER MftRecord;
2082 PNTFS_ATTR_CONTEXT IndexRootCtx;
2083 PINDEX_ROOT_ATTRIBUTE IndexRoot;
2084 PCHAR IndexRecord;
2085 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
2086 NTSTATUS Status;
2087 ULONG CurrentEntry = 0;
2088
2089 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n",
2090 Vcb,
2091 MFTIndex,
2092 FileName,
2093 *FirstEntry,
2094 DirSearch ? "TRUE" : "FALSE",
2095 CaseSensitive ? "TRUE" : "FALSE",
2096 OutMFTIndex);
2097
2098 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
2099 Vcb->NtfsInfo.BytesPerFileRecord,
2100 TAG_NTFS);
2101 if (MftRecord == NULL)
2102 {
2103 return STATUS_INSUFFICIENT_RESOURCES;
2104 }
2105
2106 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
2107 if (!NT_SUCCESS(Status))
2108 {
2109 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2110 return Status;
2111 }
2112
2113 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
2114 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
2115 if (!NT_SUCCESS(Status))
2116 {
2117 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2118 return Status;
2119 }
2120
2121 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
2122 if (IndexRecord == NULL)
2123 {
2124 ReleaseAttributeContext(IndexRootCtx);
2125 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2126 return STATUS_INSUFFICIENT_RESOURCES;
2127 }
2128
2129 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
2130 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
2131 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
2132 /* Index root is always resident. */
2133 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
2134 ReleaseAttributeContext(IndexRootCtx);
2135
2136 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
2137
2138 Status = BrowseIndexEntries(Vcb,
2139 MftRecord,
2140 IndexRecord,
2141 IndexRoot->SizeOfEntry,
2142 IndexEntry,
2143 IndexEntryEnd,
2144 FileName,
2145 FirstEntry,
2146 &CurrentEntry,
2147 DirSearch,
2148 CaseSensitive,
2149 OutMFTIndex);
2150
2151 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2152 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2153
2154 return Status;
2155 }
2156
2157 NTSTATUS
2158 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
2159 PUNICODE_STRING PathName,
2160 BOOLEAN CaseSensitive,
2161 PFILE_RECORD_HEADER *FileRecord,
2162 PULONGLONG MFTIndex,
2163 ULONGLONG CurrentMFTIndex)
2164 {
2165 UNICODE_STRING Current, Remaining;
2166 NTSTATUS Status;
2167 ULONG FirstEntry = 0;
2168
2169 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
2170 Vcb,
2171 PathName,
2172 CaseSensitive ? "TRUE" : "FALSE",
2173 FileRecord,
2174 MFTIndex,
2175 CurrentMFTIndex);
2176
2177 FsRtlDissectName(*PathName, &Current, &Remaining);
2178
2179 while (Current.Length != 0)
2180 {
2181 DPRINT("Current: %wZ\n", &Current);
2182
2183 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex);
2184 if (!NT_SUCCESS(Status))
2185 {
2186 return Status;
2187 }
2188
2189 if (Remaining.Length == 0)
2190 break;
2191
2192 FsRtlDissectName(Current, &Current, &Remaining);
2193 }
2194
2195 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2196 if (*FileRecord == NULL)
2197 {
2198 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
2199 return STATUS_INSUFFICIENT_RESOURCES;
2200 }
2201
2202 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2203 if (!NT_SUCCESS(Status))
2204 {
2205 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
2206 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2207 return Status;
2208 }
2209
2210 *MFTIndex = CurrentMFTIndex;
2211
2212 return STATUS_SUCCESS;
2213 }
2214
2215 NTSTATUS
2216 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
2217 PUNICODE_STRING PathName,
2218 BOOLEAN CaseSensitive,
2219 PFILE_RECORD_HEADER *FileRecord,
2220 PULONGLONG MFTIndex)
2221 {
2222 return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT);
2223 }
2224
2225 /**
2226 * @name NtfsDumpFileRecord
2227 * @implemented
2228 *
2229 * Provides diagnostic information about a file record. Prints a hex dump
2230 * of the entire record (based on the size reported by FileRecord->ByesInUse),
2231 * then prints a dump of each attribute.
2232 *
2233 * @param Vcb
2234 * Pointer to a DEVICE_EXTENSION describing the volume.
2235 *
2236 * @param FileRecord
2237 * Pointer to the file record to be analyzed.
2238 *
2239 * @remarks
2240 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
2241 * in size, and not just the header.
2242 *
2243 */
2244 VOID
2245 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
2246 PFILE_RECORD_HEADER FileRecord)
2247 {
2248 ULONG i, j;
2249
2250 // dump binary data, 8 bytes at a time
2251 for (i = 0; i < FileRecord->BytesInUse; i += 8)
2252 {
2253 // display current offset, in hex
2254 DbgPrint("\t%03x\t", i);
2255
2256 // display hex value of each of the next 8 bytes
2257 for (j = 0; j < 8; j++)
2258 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
2259 DbgPrint("\n");
2260 }
2261
2262 NtfsDumpFileAttributes(Vcb, FileRecord);
2263 }
2264
2265 NTSTATUS
2266 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
2267 PUNICODE_STRING SearchPattern,
2268 PULONG FirstEntry,
2269 PFILE_RECORD_HEADER *FileRecord,
2270 PULONGLONG MFTIndex,
2271 ULONGLONG CurrentMFTIndex,
2272 BOOLEAN CaseSensitive)
2273 {
2274 NTSTATUS Status;
2275
2276 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n",
2277 Vcb,
2278 SearchPattern,
2279 *FirstEntry,
2280 FileRecord,
2281 MFTIndex,
2282 CurrentMFTIndex,
2283 (CaseSensitive ? "TRUE" : "FALSE"));
2284
2285 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, CaseSensitive, &CurrentMFTIndex);
2286 if (!NT_SUCCESS(Status))
2287 {
2288 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
2289 return Status;
2290 }
2291
2292 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2293 if (*FileRecord == NULL)
2294 {
2295 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
2296 return STATUS_INSUFFICIENT_RESOURCES;
2297 }
2298
2299 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2300 if (!NT_SUCCESS(Status))
2301 {
2302 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
2303 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2304 return Status;
2305 }
2306
2307 *MFTIndex = CurrentMFTIndex;
2308
2309 return STATUS_SUCCESS;
2310 }
2311
2312 /* EOF */