[NTFS] - In the NtfsAddFilenameToDirectory() function, rename DirectoryContext parame...
[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, %u, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx);
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 << 1) == (NameLength << 1))
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((PUCHAR)((ULONG_PTR)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 }
341
342 // Cleanup
343 ExReleaseResourceLite(&(Vcb->DirResource));
344 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
345 ReleaseAttributeContext(BitmapContext);
346
347 return STATUS_SUCCESS;
348 }
349
350 VOID
351 InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
352 PFILE_RECORD_HEADER FileRecord,
353 ULONG AttrOffset,
354 ULONG DataSize)
355 {
356 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
357 ULONG NextAttributeOffset;
358
359 DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize);
360
361 // update ValueLength Field
362 AttrContext->Record.Resident.ValueLength =
363 Destination->Resident.ValueLength = DataSize;
364
365 // calculate the record length and end marker offset
366 AttrContext->Record.Length =
367 Destination->Length = DataSize + AttrContext->Record.Resident.ValueOffset;
368 NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
369
370 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
371 if (NextAttributeOffset % 8 != 0)
372 {
373 USHORT Padding = 8 - (NextAttributeOffset % 8);
374 NextAttributeOffset += Padding;
375 AttrContext->Record.Length += Padding;
376 Destination->Length += Padding;
377 }
378
379 // advance Destination to the final "attribute" and set the file record end
380 Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
381 SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END);
382 }
383
384 /**
385 * @parameter FileRecord
386 * Pointer to a file record. Must be a full record at least
387 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
388 */
389 NTSTATUS
390 SetAttributeDataLength(PFILE_OBJECT FileObject,
391 PNTFS_FCB Fcb,
392 PNTFS_ATTR_CONTEXT AttrContext,
393 ULONG AttrOffset,
394 PFILE_RECORD_HEADER FileRecord,
395 PLARGE_INTEGER DataSize)
396 {
397 NTSTATUS Status = STATUS_SUCCESS;
398
399 // are we truncating the file?
400 if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record))
401 {
402 if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize))
403 {
404 DPRINT1("Can't truncate a memory-mapped file!\n");
405 return STATUS_USER_MAPPED_FILE;
406 }
407 }
408
409 if (AttrContext->Record.IsNonResident)
410 {
411 Status = SetNonResidentAttributeDataLength(Fcb->Vcb,
412 AttrContext,
413 AttrOffset,
414 FileRecord,
415 DataSize);
416 }
417 else
418 {
419 // resident attribute
420 Status = SetResidentAttributeDataLength(Fcb->Vcb,
421 AttrContext,
422 AttrOffset,
423 FileRecord,
424 DataSize);
425 }
426
427 if (!NT_SUCCESS(Status))
428 {
429 DPRINT1("ERROR: Failed to set size of attribute!\n");
430 return Status;
431 }
432
433 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
434
435 // write the updated file record back to disk
436 Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord);
437
438 if (NT_SUCCESS(Status))
439 {
440 if(AttrContext->Record.IsNonResident)
441 Fcb->RFCB.AllocationSize.QuadPart = AttrContext->Record.NonResident.AllocatedSize;
442 else
443 Fcb->RFCB.AllocationSize = *DataSize;
444 Fcb->RFCB.FileSize = *DataSize;
445 Fcb->RFCB.ValidDataLength = *DataSize;
446 CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
447 }
448
449 return STATUS_SUCCESS;
450 }
451
452 /**
453 * @name SetFileRecordEnd
454 * @implemented
455 *
456 * This small function sets a new endpoint for the file record. It set's the final
457 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
458 *
459 * @param FileRecord
460 * Pointer to the file record whose endpoint (length) will be set.
461 *
462 * @param AttrEnd
463 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
464 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
465 *
466 * @param EndMarker
467 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
468 * a file record, it preserves the final ULONG that previously ended the record, even though this
469 * value is (to my knowledge) never used. We emulate this behavior.
470 *
471 */
472 VOID
473 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
474 PNTFS_ATTR_RECORD AttrEnd,
475 ULONG EndMarker)
476 {
477 // mark the end of attributes
478 AttrEnd->Type = AttributeEnd;
479
480 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
481 AttrEnd->Length = EndMarker;
482
483 // recalculate bytes in use
484 FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2;
485 }
486
487 /**
488 * @name SetNonResidentAttributeDataLength
489 * @implemented
490 *
491 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
492 *
493 * @param Vcb
494 * Pointer to a DEVICE_EXTENSION describing the target disk.
495 *
496 * @param AttrContext
497 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
498 *
499 * @param AttrOffset
500 * Offset, from the beginning of the record, of the attribute being sized.
501 *
502 * @param FileRecord
503 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
504 * not just the header.
505 *
506 * @param DataSize
507 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
508 *
509 * @return
510 * STATUS_SUCCESS on success;
511 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
512 * STATUS_INVALID_PARAMETER if we can't find the last cluster in the data run.
513 *
514 * @remarks
515 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
516 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
517 * any associated files. Synchronization is the callers responsibility.
518 */
519 NTSTATUS
520 SetNonResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
521 PNTFS_ATTR_CONTEXT AttrContext,
522 ULONG AttrOffset,
523 PFILE_RECORD_HEADER FileRecord,
524 PLARGE_INTEGER DataSize)
525 {
526 NTSTATUS Status = STATUS_SUCCESS;
527 ULONG BytesPerCluster = Vcb->NtfsInfo.BytesPerCluster;
528 ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster);
529 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
530 ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster;
531
532 if (!AttrContext->Record.IsNonResident)
533 {
534 DPRINT1("ERROR: SetNonResidentAttributeDataLength() called for resident attribute!\n");
535 return STATUS_INVALID_PARAMETER;
536 }
537
538 // do we need to increase the allocation size?
539 if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize)
540 {
541 ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters;
542 LARGE_INTEGER LastClusterInDataRun;
543 ULONG NextAssignedCluster;
544 ULONG AssignedClusters;
545
546 if (ExistingClusters == 0)
547 {
548 LastClusterInDataRun.QuadPart = 0;
549 }
550 else
551 {
552 if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
553 (LONGLONG)AttrContext->Record.NonResident.HighestVCN,
554 (PLONGLONG)&LastClusterInDataRun.QuadPart,
555 NULL,
556 NULL,
557 NULL,
558 NULL))
559 {
560 DPRINT1("Error looking up final large MCB entry!\n");
561
562 // Most likely, HighestVCN went above the largest mapping
563 DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
564 return STATUS_INVALID_PARAMETER;
565 }
566 }
567
568 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
569 DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
570
571 while (ClustersNeeded > 0)
572 {
573 Status = NtfsAllocateClusters(Vcb,
574 LastClusterInDataRun.LowPart + 1,
575 ClustersNeeded,
576 &NextAssignedCluster,
577 &AssignedClusters);
578
579 if (!NT_SUCCESS(Status))
580 {
581 DPRINT1("Error: Unable to allocate requested clusters!\n");
582 return Status;
583 }
584
585 // now we need to add the clusters we allocated to the data run
586 Status = AddRun(Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters);
587 if (!NT_SUCCESS(Status))
588 {
589 DPRINT1("Error: Unable to add data run!\n");
590 return Status;
591 }
592
593 ClustersNeeded -= AssignedClusters;
594 LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1;
595 }
596 }
597 else if (AttrContext->Record.NonResident.AllocatedSize > AllocationSize)
598 {
599 // shrink allocation size
600 ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster);
601 Status = FreeClusters(Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree);
602 }
603
604 // TODO: is the file compressed, encrypted, or sparse?
605
606 AttrContext->Record.NonResident.AllocatedSize = AllocationSize;
607 AttrContext->Record.NonResident.DataSize = DataSize->QuadPart;
608 AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
609
610 DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
611 DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
612 DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
613
614 DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
615
616 return Status;
617 }
618
619 /**
620 * @name SetResidentAttributeDataLength
621 * @implemented
622 *
623 * Called by SetAttributeDataLength() to set the size of a non-resident attribute. Doesn't update the file record.
624 *
625 * @param Vcb
626 * Pointer to a DEVICE_EXTENSION describing the target disk.
627 *
628 * @param AttrContext
629 * PNTFS_ATTR_CONTEXT describing the location of the attribute whose size is being set.
630 *
631 * @param AttrOffset
632 * Offset, from the beginning of the record, of the attribute being sized.
633 *
634 * @param FileRecord
635 * Pointer to a file record containing the attribute to be resized. Must be a complete file record,
636 * not just the header.
637 *
638 * @param DataSize
639 * Pointer to a LARGE_INTEGER describing the new size of the attribute's data.
640 *
641 * @return
642 * STATUS_SUCCESS on success;
643 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
644 * STATUS_INVALID_PARAMETER if AttrContext describes a non-resident attribute.
645 * STATUS_NOT_IMPLEMENTED if requested to decrease the size of an attribute that isn't the
646 * last attribute listed in the file record.
647 *
648 * @remarks
649 * Called by SetAttributeDataLength() and IncreaseMftSize(). Use SetAttributeDataLength() unless you have a good
650 * reason to use this. Doesn't update the file record on disk. Doesn't inform the cache controller of changes with
651 * any associated files. Synchronization is the callers responsibility.
652 */
653 NTSTATUS
654 SetResidentAttributeDataLength(PDEVICE_EXTENSION Vcb,
655 PNTFS_ATTR_CONTEXT AttrContext,
656 ULONG AttrOffset,
657 PFILE_RECORD_HEADER FileRecord,
658 PLARGE_INTEGER DataSize)
659 {
660 NTSTATUS Status;
661
662 // find the next attribute
663 ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
664 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
665
666 if (AttrContext->Record.IsNonResident)
667 {
668 DPRINT1("ERROR: SetResidentAttributeDataLength() called for non-resident attribute!\n");
669 return STATUS_INVALID_PARAMETER;
670 }
671
672 //NtfsDumpFileAttributes(Vcb, FileRecord);
673
674 // Do we need to increase the data length?
675 if (DataSize->QuadPart > AttrContext->Record.Resident.ValueLength)
676 {
677 // There's usually padding at the end of a record. Do we need to extend past it?
678 ULONG MaxValueLength = AttrContext->Record.Length - AttrContext->Record.Resident.ValueOffset;
679 if (MaxValueLength < DataSize->LowPart)
680 {
681 // If this is the last attribute, we could move the end marker to the very end of the file record
682 MaxValueLength += Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
683
684 if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
685 {
686 // convert attribute to non-resident
687 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
688 LARGE_INTEGER AttribDataSize;
689 PVOID AttribData;
690 ULONG EndAttributeOffset;
691 ULONG LengthWritten;
692
693 DPRINT1("Converting attribute to non-resident.\n");
694
695 AttribDataSize.QuadPart = AttrContext->Record.Resident.ValueLength;
696
697 // Is there existing data we need to back-up?
698 if (AttribDataSize.QuadPart > 0)
699 {
700 AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
701 if (AttribData == NULL)
702 {
703 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
704 return STATUS_INSUFFICIENT_RESOURCES;
705 }
706
707 // read data to temp buffer
708 Status = ReadAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
709 if (!NT_SUCCESS(Status))
710 {
711 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
712 ExFreePoolWithTag(AttribData, TAG_NTFS);
713 return Status;
714 }
715 }
716
717 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
718
719 // Zero out the NonResident structure
720 RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN,
721 FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
722 RtlZeroMemory(&Destination->NonResident.LowestVCN,
723 FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
724
725 // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
726 AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2);
727
728 // mark the attribute as non-resident
729 AttrContext->Record.IsNonResident = Destination->IsNonResident = 1;
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, 8);
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 // update file record on disk
746 Status = UpdateFileRecord(Vcb, AttrContext->FileMFTIndex, FileRecord);
747 if (!NT_SUCCESS(Status))
748 {
749 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
750 if (AttribDataSize.QuadPart > 0)
751 ExFreePoolWithTag(AttribData, TAG_NTFS);
752 return Status;
753 }
754
755 // Initialize the MCB, potentially catch an exception
756 _SEH2_TRY{
757 FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
758 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
759 _SEH2_YIELD(return _SEH2_GetExceptionCode());
760 } _SEH2_END;
761
762 // Now we can treat the attribute as non-resident and enlarge it normally
763 Status = SetNonResidentAttributeDataLength(Vcb, AttrContext, AttrOffset, FileRecord, DataSize);
764 if (!NT_SUCCESS(Status))
765 {
766 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
767 if (AttribDataSize.QuadPart > 0)
768 ExFreePoolWithTag(AttribData, TAG_NTFS);
769 return Status;
770 }
771
772 // restore the back-up attribute, if we made one
773 if (AttribDataSize.QuadPart > 0)
774 {
775 Status = WriteAttribute(Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
776 if (!NT_SUCCESS(Status))
777 {
778 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
779 // TODO: Reverse migration so no data is lost
780 ExFreePoolWithTag(AttribData, TAG_NTFS);
781 return Status;
782 }
783
784 ExFreePoolWithTag(AttribData, TAG_NTFS);
785 }
786 }
787 }
788 }
789 else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength)
790 {
791 // we need to decrease the length
792 if (NextAttribute->Type != AttributeEnd)
793 {
794 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
795 return STATUS_NOT_IMPLEMENTED;
796 }
797 }
798
799 // set the new length of the resident attribute (if we didn't migrate it)
800 if (!AttrContext->Record.IsNonResident)
801 InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
802
803 return STATUS_SUCCESS;
804 }
805
806 ULONG
807 ReadAttribute(PDEVICE_EXTENSION Vcb,
808 PNTFS_ATTR_CONTEXT Context,
809 ULONGLONG Offset,
810 PCHAR Buffer,
811 ULONG Length)
812 {
813 ULONGLONG LastLCN;
814 PUCHAR DataRun;
815 LONGLONG DataRunOffset;
816 ULONGLONG DataRunLength;
817 LONGLONG DataRunStartLCN;
818 ULONGLONG CurrentOffset;
819 ULONG ReadLength;
820 ULONG AlreadyRead;
821 NTSTATUS Status;
822
823 //TEMPTEMP
824 PUCHAR TempBuffer;
825
826 if (!Context->Record.IsNonResident)
827 {
828 if (Offset > Context->Record.Resident.ValueLength)
829 return 0;
830 if (Offset + Length > Context->Record.Resident.ValueLength)
831 Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
832 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
833 return Length;
834 }
835
836 /*
837 * Non-resident attribute
838 */
839
840 /*
841 * I. Find the corresponding start data run.
842 */
843
844 AlreadyRead = 0;
845
846 // FIXME: Cache seems to be non-working. Disable it for now
847 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
848 if (0)
849 {
850 DataRun = Context->CacheRun;
851 LastLCN = Context->CacheRunLastLCN;
852 DataRunStartLCN = Context->CacheRunStartLCN;
853 DataRunLength = Context->CacheRunLength;
854 CurrentOffset = Context->CacheRunCurrentOffset;
855 }
856 else
857 {
858 //TEMPTEMP
859 ULONG UsedBufferSize;
860 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
861
862 LastLCN = 0;
863 CurrentOffset = 0;
864
865 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
866 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
867 TempBuffer,
868 Vcb->NtfsInfo.BytesPerFileRecord,
869 &UsedBufferSize);
870
871 DataRun = TempBuffer;
872
873 while (1)
874 {
875 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
876 if (DataRunOffset != -1)
877 {
878 /* Normal data run. */
879 DataRunStartLCN = LastLCN + DataRunOffset;
880 LastLCN = DataRunStartLCN;
881 }
882 else
883 {
884 /* Sparse data run. */
885 DataRunStartLCN = -1;
886 }
887
888 if (Offset >= CurrentOffset &&
889 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
890 {
891 break;
892 }
893
894 if (*DataRun == 0)
895 {
896 return AlreadyRead;
897 }
898
899 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
900 }
901 }
902
903 /*
904 * II. Go through the run list and read the data
905 */
906
907 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
908 if (DataRunStartLCN == -1)
909 {
910 RtlZeroMemory(Buffer, ReadLength);
911 Status = STATUS_SUCCESS;
912 }
913 else
914 {
915 Status = NtfsReadDisk(Vcb->StorageDevice,
916 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
917 ReadLength,
918 Vcb->NtfsInfo.BytesPerSector,
919 (PVOID)Buffer,
920 FALSE);
921 }
922 if (NT_SUCCESS(Status))
923 {
924 Length -= ReadLength;
925 Buffer += ReadLength;
926 AlreadyRead += ReadLength;
927
928 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
929 {
930 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
931 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
932 if (DataRunOffset != (ULONGLONG)-1)
933 {
934 DataRunStartLCN = LastLCN + DataRunOffset;
935 LastLCN = DataRunStartLCN;
936 }
937 else
938 DataRunStartLCN = -1;
939 }
940
941 while (Length > 0)
942 {
943 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
944 if (DataRunStartLCN == -1)
945 RtlZeroMemory(Buffer, ReadLength);
946 else
947 {
948 Status = NtfsReadDisk(Vcb->StorageDevice,
949 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
950 ReadLength,
951 Vcb->NtfsInfo.BytesPerSector,
952 (PVOID)Buffer,
953 FALSE);
954 if (!NT_SUCCESS(Status))
955 break;
956 }
957
958 Length -= ReadLength;
959 Buffer += ReadLength;
960 AlreadyRead += ReadLength;
961
962 /* We finished this request, but there still data in this data run. */
963 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
964 break;
965
966 /*
967 * Go to next run in the list.
968 */
969
970 if (*DataRun == 0)
971 break;
972 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
973 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
974 if (DataRunOffset != -1)
975 {
976 /* Normal data run. */
977 DataRunStartLCN = LastLCN + DataRunOffset;
978 LastLCN = DataRunStartLCN;
979 }
980 else
981 {
982 /* Sparse data run. */
983 DataRunStartLCN = -1;
984 }
985 } /* while */
986
987 } /* if Disk */
988
989 // TEMPTEMP
990 if (Context->Record.IsNonResident)
991 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
992
993 Context->CacheRun = DataRun;
994 Context->CacheRunOffset = Offset + AlreadyRead;
995 Context->CacheRunStartLCN = DataRunStartLCN;
996 Context->CacheRunLength = DataRunLength;
997 Context->CacheRunLastLCN = LastLCN;
998 Context->CacheRunCurrentOffset = CurrentOffset;
999
1000 return AlreadyRead;
1001 }
1002
1003
1004 /**
1005 * @name WriteAttribute
1006 * @implemented
1007 *
1008 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
1009 * and it still needs more documentation / cleaning up.
1010 *
1011 * @param Vcb
1012 * Volume Control Block indicating which volume to write the attribute to
1013 *
1014 * @param Context
1015 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
1016 *
1017 * @param Offset
1018 * Offset, in bytes, from the beginning of the attribute indicating where to start
1019 * writing data
1020 *
1021 * @param Buffer
1022 * The data that's being written to the device
1023 *
1024 * @param Length
1025 * How much data will be written, in bytes
1026 *
1027 * @param RealLengthWritten
1028 * Pointer to a ULONG which will receive how much data was written, in bytes
1029 *
1030 * @return
1031 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
1032 * writing to a sparse file.
1033 *
1034 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
1035 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
1036 *
1037 */
1038
1039 NTSTATUS
1040 WriteAttribute(PDEVICE_EXTENSION Vcb,
1041 PNTFS_ATTR_CONTEXT Context,
1042 ULONGLONG Offset,
1043 const PUCHAR Buffer,
1044 ULONG Length,
1045 PULONG RealLengthWritten)
1046 {
1047 ULONGLONG LastLCN;
1048 PUCHAR DataRun;
1049 LONGLONG DataRunOffset;
1050 ULONGLONG DataRunLength;
1051 LONGLONG DataRunStartLCN;
1052 ULONGLONG CurrentOffset;
1053 ULONG WriteLength;
1054 NTSTATUS Status;
1055 PUCHAR SourceBuffer = Buffer;
1056 LONGLONG StartingOffset;
1057
1058 //TEMPTEMP
1059 PUCHAR TempBuffer;
1060
1061
1062 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten);
1063
1064 *RealLengthWritten = 0;
1065
1066 // is this a resident attribute?
1067 if (!Context->Record.IsNonResident)
1068 {
1069 ULONG AttributeOffset;
1070 PNTFS_ATTR_CONTEXT FoundContext;
1071 PFILE_RECORD_HEADER FileRecord;
1072
1073 if (Offset + Length > Context->Record.Resident.ValueLength)
1074 {
1075 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
1076 return STATUS_INVALID_PARAMETER;
1077 }
1078
1079 FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1080
1081 if (!FileRecord)
1082 {
1083 DPRINT1("Error: Couldn't allocate file record!\n");
1084 return STATUS_NO_MEMORY;
1085 }
1086
1087 // read the file record
1088 ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1089
1090 // find where to write the attribute data to
1091 Status = FindAttribute(Vcb, FileRecord,
1092 Context->Record.Type,
1093 (PCWSTR)((PCHAR)&Context->Record + Context->Record.NameOffset),
1094 Context->Record.NameLength,
1095 &FoundContext,
1096 &AttributeOffset);
1097
1098 if (!NT_SUCCESS(Status))
1099 {
1100 DPRINT1("ERROR: Couldn't find matching attribute!\n");
1101 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1102 return Status;
1103 }
1104
1105 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->Record.Resident.ValueLength);
1106 Offset += AttributeOffset + Context->Record.Resident.ValueOffset;
1107
1108 if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord)
1109 {
1110 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
1111 ReleaseAttributeContext(FoundContext);
1112 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1113 return STATUS_INVALID_PARAMETER;
1114 }
1115
1116 // copy the data being written into the file record
1117 RtlCopyMemory((PCHAR)FileRecord + Offset, Buffer, Length);
1118
1119 Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
1120
1121 ReleaseAttributeContext(FoundContext);
1122 ExFreePoolWithTag(FileRecord, TAG_NTFS);
1123
1124 if (NT_SUCCESS(Status))
1125 *RealLengthWritten = Length;
1126
1127 return Status;
1128 }
1129
1130 // This is a non-resident attribute.
1131
1132 // I. Find the corresponding start data run.
1133
1134 // FIXME: Cache seems to be non-working. Disable it for now
1135 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
1136 /*if (0)
1137 {
1138 DataRun = Context->CacheRun;
1139 LastLCN = Context->CacheRunLastLCN;
1140 DataRunStartLCN = Context->CacheRunStartLCN;
1141 DataRunLength = Context->CacheRunLength;
1142 CurrentOffset = Context->CacheRunCurrentOffset;
1143 }
1144 else*/
1145 {
1146 ULONG UsedBufferSize;
1147 LastLCN = 0;
1148 CurrentOffset = 0;
1149
1150 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
1151 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1152
1153 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
1154 TempBuffer,
1155 Vcb->NtfsInfo.BytesPerFileRecord,
1156 &UsedBufferSize);
1157
1158 DataRun = TempBuffer;
1159
1160 while (1)
1161 {
1162 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1163 if (DataRunOffset != -1)
1164 {
1165 // Normal data run.
1166 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
1167 DataRunStartLCN = LastLCN + DataRunOffset;
1168 LastLCN = DataRunStartLCN;
1169 }
1170 else
1171 {
1172 // Sparse data run. We can't support writing to sparse files yet
1173 // (it may require increasing the allocation size).
1174 DataRunStartLCN = -1;
1175 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
1176 return STATUS_NOT_IMPLEMENTED;
1177 }
1178
1179 // Have we reached the data run we're trying to write to?
1180 if (Offset >= CurrentOffset &&
1181 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
1182 {
1183 break;
1184 }
1185
1186 if (*DataRun == 0)
1187 {
1188 // We reached the last assigned cluster
1189 // TODO: assign new clusters to the end of the file.
1190 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
1191 // [We can reach here by creating a new file record when the MFT isn't large enough]
1192 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
1193 return STATUS_END_OF_FILE;
1194 }
1195
1196 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1197 }
1198 }
1199
1200 // II. Go through the run list and write the data
1201
1202 /* REVIEWME -- As adapted from NtfsReadAttribute():
1203 We seem to be making a special case for the first applicable data run, but I'm not sure why.
1204 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
1205
1206 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
1207
1208 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
1209
1210 // Write the data to the disk
1211 Status = NtfsWriteDisk(Vcb->StorageDevice,
1212 StartingOffset,
1213 WriteLength,
1214 Vcb->NtfsInfo.BytesPerSector,
1215 (PVOID)SourceBuffer);
1216
1217 // Did the write fail?
1218 if (!NT_SUCCESS(Status))
1219 {
1220 Context->CacheRun = DataRun;
1221 Context->CacheRunOffset = Offset;
1222 Context->CacheRunStartLCN = DataRunStartLCN;
1223 Context->CacheRunLength = DataRunLength;
1224 Context->CacheRunLastLCN = LastLCN;
1225 Context->CacheRunCurrentOffset = CurrentOffset;
1226
1227 return Status;
1228 }
1229
1230 Length -= WriteLength;
1231 SourceBuffer += WriteLength;
1232 *RealLengthWritten += WriteLength;
1233
1234 // Did we write to the end of the data run?
1235 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
1236 {
1237 // Advance to the next data run
1238 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1239 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1240
1241 if (DataRunOffset != (ULONGLONG)-1)
1242 {
1243 DataRunStartLCN = LastLCN + DataRunOffset;
1244 LastLCN = DataRunStartLCN;
1245 }
1246 else
1247 DataRunStartLCN = -1;
1248 }
1249
1250 // Do we have more data to write?
1251 while (Length > 0)
1252 {
1253 // Make sure we don't write past the end of the current data run
1254 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
1255
1256 // Are we dealing with a sparse data run?
1257 if (DataRunStartLCN == -1)
1258 {
1259 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
1260 return STATUS_NOT_IMPLEMENTED;
1261 }
1262 else
1263 {
1264 // write the data to the disk
1265 Status = NtfsWriteDisk(Vcb->StorageDevice,
1266 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
1267 WriteLength,
1268 Vcb->NtfsInfo.BytesPerSector,
1269 (PVOID)SourceBuffer);
1270 if (!NT_SUCCESS(Status))
1271 break;
1272 }
1273
1274 Length -= WriteLength;
1275 SourceBuffer += WriteLength;
1276 *RealLengthWritten += WriteLength;
1277
1278 // We finished this request, but there's still data in this data run.
1279 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1280 break;
1281
1282 // Go to next run in the list.
1283
1284 if (*DataRun == 0)
1285 {
1286 // that was the last run
1287 if (Length > 0)
1288 {
1289 // Failed sanity check.
1290 DPRINT1("Encountered EOF before expected!\n");
1291 return STATUS_END_OF_FILE;
1292 }
1293
1294 break;
1295 }
1296
1297 // Advance to the next data run
1298 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1299 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1300 if (DataRunOffset != -1)
1301 {
1302 // Normal data run.
1303 DataRunStartLCN = LastLCN + DataRunOffset;
1304 LastLCN = DataRunStartLCN;
1305 }
1306 else
1307 {
1308 // Sparse data run.
1309 DataRunStartLCN = -1;
1310 }
1311 } // end while (Length > 0) [more data to write]
1312
1313 // TEMPTEMP
1314 if(Context->Record.IsNonResident)
1315 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1316
1317 Context->CacheRun = DataRun;
1318 Context->CacheRunOffset = Offset + *RealLengthWritten;
1319 Context->CacheRunStartLCN = DataRunStartLCN;
1320 Context->CacheRunLength = DataRunLength;
1321 Context->CacheRunLastLCN = LastLCN;
1322 Context->CacheRunCurrentOffset = CurrentOffset;
1323
1324 return Status;
1325 }
1326
1327 NTSTATUS
1328 ReadFileRecord(PDEVICE_EXTENSION Vcb,
1329 ULONGLONG index,
1330 PFILE_RECORD_HEADER file)
1331 {
1332 ULONGLONG BytesRead;
1333
1334 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
1335
1336 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
1337 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
1338 {
1339 DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
1340 return STATUS_PARTIAL_COPY;
1341 }
1342
1343 /* Apply update sequence array fixups. */
1344 DPRINT("Sequence number: %u\n", file->SequenceNumber);
1345 return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
1346 }
1347
1348
1349 /**
1350 * Searches a file's parent directory (given the parent's index in the mft)
1351 * for the given file. Upon finding an index entry for that file, updates
1352 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1353 *
1354 * (Most of this code was copied from NtfsFindMftRecord)
1355 */
1356 NTSTATUS
1357 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
1358 ULONGLONG ParentMFTIndex,
1359 PUNICODE_STRING FileName,
1360 BOOLEAN DirSearch,
1361 ULONGLONG NewDataSize,
1362 ULONGLONG NewAllocationSize,
1363 BOOLEAN CaseSensitive)
1364 {
1365 PFILE_RECORD_HEADER MftRecord;
1366 PNTFS_ATTR_CONTEXT IndexRootCtx;
1367 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1368 PCHAR IndexRecord;
1369 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1370 NTSTATUS Status;
1371 ULONG CurrentEntry = 0;
1372
1373 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u, %s)\n",
1374 Vcb,
1375 ParentMFTIndex,
1376 FileName,
1377 DirSearch,
1378 NewDataSize,
1379 NewAllocationSize,
1380 CaseSensitive ? "TRUE" : "FALSE");
1381
1382 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1383 Vcb->NtfsInfo.BytesPerFileRecord,
1384 TAG_NTFS);
1385 if (MftRecord == NULL)
1386 {
1387 return STATUS_INSUFFICIENT_RESOURCES;
1388 }
1389
1390 Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
1391 if (!NT_SUCCESS(Status))
1392 {
1393 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1394 return Status;
1395 }
1396
1397 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1398 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1399 if (!NT_SUCCESS(Status))
1400 {
1401 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1402 return Status;
1403 }
1404
1405 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1406 if (IndexRecord == NULL)
1407 {
1408 ReleaseAttributeContext(IndexRootCtx);
1409 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1410 return STATUS_INSUFFICIENT_RESOURCES;
1411 }
1412
1413 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
1414 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1415 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1416 // Index root is always resident.
1417 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1418
1419 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1420
1421 Status = UpdateIndexEntryFileNameSize(Vcb,
1422 MftRecord,
1423 IndexRecord,
1424 IndexRoot->SizeOfEntry,
1425 IndexEntry,
1426 IndexEntryEnd,
1427 FileName,
1428 &CurrentEntry,
1429 &CurrentEntry,
1430 DirSearch,
1431 NewDataSize,
1432 NewAllocationSize,
1433 CaseSensitive);
1434
1435 ReleaseAttributeContext(IndexRootCtx);
1436 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1437 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1438
1439 return Status;
1440 }
1441
1442 /**
1443 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1444 * proper index entry.
1445 * (Heavily based on BrowseIndexEntries)
1446 */
1447 NTSTATUS
1448 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
1449 PFILE_RECORD_HEADER MftRecord,
1450 PCHAR IndexRecord,
1451 ULONG IndexBlockSize,
1452 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1453 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1454 PUNICODE_STRING FileName,
1455 PULONG StartEntry,
1456 PULONG CurrentEntry,
1457 BOOLEAN DirSearch,
1458 ULONGLONG NewDataSize,
1459 ULONGLONG NewAllocatedSize,
1460 BOOLEAN CaseSensitive)
1461 {
1462 NTSTATUS Status;
1463 ULONG RecordOffset;
1464 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1465 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1466 ULONGLONG IndexAllocationSize;
1467 PINDEX_BUFFER IndexBuffer;
1468
1469 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %u, %I64u, %I64u)\n", Vcb, MftRecord, IndexRecord, IndexBlockSize, FirstEntry, LastEntry, FileName, *StartEntry, *CurrentEntry, DirSearch, NewDataSize, NewAllocatedSize);
1470
1471 // find the index entry responsible for the file we're trying to update
1472 IndexEntry = FirstEntry;
1473 while (IndexEntry < LastEntry &&
1474 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1475 {
1476 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
1477 *CurrentEntry >= *StartEntry &&
1478 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1479 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
1480 {
1481 *StartEntry = *CurrentEntry;
1482 IndexEntry->FileName.DataSize = NewDataSize;
1483 IndexEntry->FileName.AllocatedSize = NewAllocatedSize;
1484 // indicate that the caller will still need to write the structure to the disk
1485 return STATUS_PENDING;
1486 }
1487
1488 (*CurrentEntry) += 1;
1489 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1490 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1491 }
1492
1493 /* If we're already browsing a subnode */
1494 if (IndexRecord == NULL)
1495 {
1496 return STATUS_OBJECT_PATH_NOT_FOUND;
1497 }
1498
1499 /* If there's no subnode */
1500 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1501 {
1502 return STATUS_OBJECT_PATH_NOT_FOUND;
1503 }
1504
1505 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1506 if (!NT_SUCCESS(Status))
1507 {
1508 DPRINT("Corrupted filesystem!\n");
1509 return Status;
1510 }
1511
1512 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1513 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1514 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1515 {
1516 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1517 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1518 if (!NT_SUCCESS(Status))
1519 {
1520 break;
1521 }
1522
1523 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1524 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1525 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1526 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1527 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1528 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1529
1530 Status = UpdateIndexEntryFileNameSize(NULL,
1531 NULL,
1532 NULL,
1533 0,
1534 FirstEntry,
1535 LastEntry,
1536 FileName,
1537 StartEntry,
1538 CurrentEntry,
1539 DirSearch,
1540 NewDataSize,
1541 NewAllocatedSize,
1542 CaseSensitive);
1543 if (Status == STATUS_PENDING)
1544 {
1545 // write the index record back to disk
1546 ULONG Written;
1547
1548 // first we need to update the fixup values for the index block
1549 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1550 if (!NT_SUCCESS(Status))
1551 {
1552 DPRINT1("Error: Failed to update fixup sequence array!\n");
1553 break;
1554 }
1555
1556 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written);
1557 if (!NT_SUCCESS(Status))
1558 {
1559 DPRINT1("ERROR Performing write!\n");
1560 break;
1561 }
1562
1563 Status = STATUS_SUCCESS;
1564 break;
1565 }
1566 if (NT_SUCCESS(Status))
1567 {
1568 break;
1569 }
1570 }
1571
1572 ReleaseAttributeContext(IndexAllocationCtx);
1573 return Status;
1574 }
1575
1576 /**
1577 * @name UpdateFileRecord
1578 * @implemented
1579 *
1580 * Writes a file record to the master file table, at a given index.
1581 *
1582 * @param Vcb
1583 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1584 *
1585 * @param MftIndex
1586 * Target index in the master file table to store the file record.
1587 *
1588 * @param FileRecord
1589 * Pointer to the complete file record which will be written to the master file table.
1590 *
1591 * @return
1592 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1593 *
1594 */
1595 NTSTATUS
1596 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
1597 ULONGLONG MftIndex,
1598 PFILE_RECORD_HEADER FileRecord)
1599 {
1600 ULONG BytesWritten;
1601 NTSTATUS Status = STATUS_SUCCESS;
1602
1603 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord);
1604
1605 // Add the fixup array to prepare the data for writing to disk
1606 AddFixupArray(Vcb, &FileRecord->Ntfs);
1607
1608 // write the file record to the master file table
1609 Status = WriteAttribute(Vcb, Vcb->MFTContext, MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)FileRecord, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
1610
1611 if (!NT_SUCCESS(Status))
1612 {
1613 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
1614 }
1615
1616 // remove the fixup array (so the file record pointer can still be used)
1617 FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs);
1618
1619 return Status;
1620 }
1621
1622
1623 NTSTATUS
1624 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
1625 PNTFS_RECORD_HEADER Record)
1626 {
1627 USHORT *USA;
1628 USHORT USANumber;
1629 USHORT USACount;
1630 USHORT *Block;
1631
1632 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
1633 USANumber = *(USA++);
1634 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
1635 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
1636
1637 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
1638
1639 while (USACount)
1640 {
1641 if (*Block != USANumber)
1642 {
1643 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
1644 return STATUS_UNSUCCESSFUL;
1645 }
1646 *Block = *(USA++);
1647 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
1648 USACount--;
1649 }
1650
1651 return STATUS_SUCCESS;
1652 }
1653
1654 /**
1655 * @name AddNewMftEntry
1656 * @implemented
1657 *
1658 * Adds a file record to the master file table of a given device.
1659 *
1660 * @param FileRecord
1661 * Pointer to a complete file record which will be saved to disk.
1662 *
1663 * @param DeviceExt
1664 * Pointer to the DEVICE_EXTENSION of the target drive.
1665 *
1666 * @param DestinationIndex
1667 * Pointer to a ULONGLONG which will receive the MFT index where the file record was stored.
1668 *
1669 * @param CanWait
1670 * Boolean indicating if the function is allowed to wait for exclusive access to the master file table.
1671 * This will only be relevant if the MFT doesn't have any free file records and needs to be enlarged.
1672 *
1673 * @return
1674 * STATUS_SUCCESS on success.
1675 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1676 * to read the attribute.
1677 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1678 * STATUS_CANT_WAIT if CanWait was FALSE and the function could not get immediate, exclusive access to the MFT.
1679 */
1680 NTSTATUS
1681 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
1682 PDEVICE_EXTENSION DeviceExt,
1683 PULONGLONG DestinationIndex,
1684 BOOLEAN CanWait)
1685 {
1686 NTSTATUS Status = STATUS_SUCCESS;
1687 ULONGLONG MftIndex;
1688 RTL_BITMAP Bitmap;
1689 ULONGLONG BitmapDataSize;
1690 ULONGLONG AttrBytesRead;
1691 PVOID BitmapData;
1692 ULONG LengthWritten;
1693 PNTFS_ATTR_CONTEXT BitmapContext;
1694 LARGE_INTEGER BitmapBits;
1695 UCHAR SystemReservedBits;
1696
1697 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
1698
1699 // First, we have to read the mft's $Bitmap attribute
1700 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
1701 if (!NT_SUCCESS(Status))
1702 {
1703 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1704 return Status;
1705 }
1706
1707 // allocate a buffer for the $Bitmap attribute
1708 BitmapDataSize = AttributeDataLength(&BitmapContext->Record);
1709 BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS);
1710 if (!BitmapData)
1711 {
1712 ReleaseAttributeContext(BitmapContext);
1713 return STATUS_INSUFFICIENT_RESOURCES;
1714 }
1715
1716 // read $Bitmap attribute
1717 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize);
1718
1719 if (AttrBytesRead == 0)
1720 {
1721 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1722 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1723 ReleaseAttributeContext(BitmapContext);
1724 return STATUS_OBJECT_NAME_NOT_FOUND;
1725 }
1726
1727 // we need to backup the bits for records 0x10 - 0x17 and leave them unassigned if they aren't assigned
1728 RtlCopyMemory(&SystemReservedBits, (PVOID)((ULONG_PTR)BitmapData + 2), 1);
1729 RtlFillMemory((PVOID)((ULONG_PTR)BitmapData + 2), 1, (UCHAR)0xFF);
1730
1731 // Calculate bit count
1732 BitmapBits.QuadPart = AttributeDataLength(&(DeviceExt->MFTContext->Record)) /
1733 DeviceExt->NtfsInfo.BytesPerFileRecord;
1734 if (BitmapBits.HighPart != 0)
1735 {
1736 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported!\n");
1737 BitmapBits.LowPart = 0xFFFFFFFF;
1738 }
1739
1740 // convert buffer into bitmap
1741 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart);
1742
1743 // set next available bit, preferrably after 23rd bit
1744 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
1745 if ((LONG)MftIndex == -1)
1746 {
1747 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
1748
1749 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1750 ReleaseAttributeContext(BitmapContext);
1751
1752 // Couldn't find a free record in the MFT, add some blank records and try again
1753 Status = IncreaseMftSize(DeviceExt, CanWait);
1754 if (!NT_SUCCESS(Status))
1755 {
1756 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
1757 return Status;
1758 }
1759
1760 return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait);
1761 }
1762
1763 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
1764
1765 // update file record with index
1766 FileRecord->MFTRecordNumber = MftIndex;
1767
1768 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1769
1770 // Restore the system reserved bits
1771 RtlCopyMemory((PVOID)((ULONG_PTR)BitmapData + 2), &SystemReservedBits, 1);
1772
1773 // write the bitmap back to the MFT's $Bitmap attribute
1774 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten);
1775 if (!NT_SUCCESS(Status))
1776 {
1777 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1778 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1779 ReleaseAttributeContext(BitmapContext);
1780 return Status;
1781 }
1782
1783 // update the file record (write it to disk)
1784 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
1785
1786 if (!NT_SUCCESS(Status))
1787 {
1788 DPRINT1("ERROR: Unable to write file record!\n");
1789 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1790 ReleaseAttributeContext(BitmapContext);
1791 return Status;
1792 }
1793
1794 *DestinationIndex = MftIndex;
1795
1796 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1797 ReleaseAttributeContext(BitmapContext);
1798
1799 return Status;
1800 }
1801
1802 NTSTATUS
1803 AddFixupArray(PDEVICE_EXTENSION Vcb,
1804 PNTFS_RECORD_HEADER Record)
1805 {
1806 USHORT *pShortToFixUp;
1807 unsigned int ArrayEntryCount = Record->UsaCount - 1;
1808 unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2;
1809 int i;
1810
1811 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
1812
1813 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
1814
1815 fixupArray->USN++;
1816
1817 for (i = 0; i < ArrayEntryCount; i++)
1818 {
1819 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
1820
1821 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
1822 fixupArray->Array[i] = *pShortToFixUp;
1823 *pShortToFixUp = fixupArray->USN;
1824 Offset += Vcb->NtfsInfo.BytesPerSector;
1825 }
1826
1827 return STATUS_SUCCESS;
1828 }
1829
1830 NTSTATUS
1831 ReadLCN(PDEVICE_EXTENSION Vcb,
1832 ULONGLONG lcn,
1833 ULONG count,
1834 PVOID buffer)
1835 {
1836 LARGE_INTEGER DiskSector;
1837
1838 DiskSector.QuadPart = lcn;
1839
1840 return NtfsReadSectors(Vcb->StorageDevice,
1841 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
1842 count * Vcb->NtfsInfo.SectorsPerCluster,
1843 Vcb->NtfsInfo.BytesPerSector,
1844 buffer,
1845 FALSE);
1846 }
1847
1848
1849 BOOLEAN
1850 CompareFileName(PUNICODE_STRING FileName,
1851 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
1852 BOOLEAN DirSearch,
1853 BOOLEAN CaseSensitive)
1854 {
1855 BOOLEAN Ret, Alloc = FALSE;
1856 UNICODE_STRING EntryName;
1857
1858 EntryName.Buffer = IndexEntry->FileName.Name;
1859 EntryName.Length =
1860 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
1861
1862 if (DirSearch)
1863 {
1864 UNICODE_STRING IntFileName;
1865 if (!CaseSensitive)
1866 {
1867 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
1868 Alloc = TRUE;
1869 }
1870 else
1871 {
1872 IntFileName = *FileName;
1873 }
1874
1875 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL);
1876
1877 if (Alloc)
1878 {
1879 RtlFreeUnicodeString(&IntFileName);
1880 }
1881
1882 return Ret;
1883 }
1884 else
1885 {
1886 return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0);
1887 }
1888 }
1889
1890 #if 0
1891 static
1892 VOID
1893 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
1894 {
1895 DPRINT1("Entry: %p\n", IndexEntry);
1896 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
1897 DPRINT1("\tLength: %u\n", IndexEntry->Length);
1898 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
1899 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
1900 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
1901 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
1902 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
1903 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
1904 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
1905 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
1906 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
1907 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
1908 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
1909 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
1910 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
1911 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
1912 }
1913 #endif
1914
1915 NTSTATUS
1916 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
1917 PFILE_RECORD_HEADER MftRecord,
1918 PCHAR IndexRecord,
1919 ULONG IndexBlockSize,
1920 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1921 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1922 PUNICODE_STRING FileName,
1923 PULONG StartEntry,
1924 PULONG CurrentEntry,
1925 BOOLEAN DirSearch,
1926 BOOLEAN CaseSensitive,
1927 ULONGLONG *OutMFTIndex)
1928 {
1929 NTSTATUS Status;
1930 ULONG RecordOffset;
1931 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1932 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1933 ULONGLONG IndexAllocationSize;
1934 PINDEX_BUFFER IndexBuffer;
1935
1936 DPRINT("BrowseIndexEntries(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %s, %s, %p)\n",
1937 Vcb,
1938 MftRecord,
1939 IndexRecord,
1940 IndexBlockSize,
1941 FirstEntry,
1942 LastEntry,
1943 FileName,
1944 *StartEntry,
1945 *CurrentEntry,
1946 DirSearch ? "TRUE" : "FALSE",
1947 CaseSensitive ? "TRUE" : "FALSE",
1948 OutMFTIndex);
1949
1950 IndexEntry = FirstEntry;
1951 while (IndexEntry < LastEntry &&
1952 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1953 {
1954 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= 0x10 &&
1955 *CurrentEntry >= *StartEntry &&
1956 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1957 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
1958 {
1959 *StartEntry = *CurrentEntry;
1960 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
1961 return STATUS_SUCCESS;
1962 }
1963
1964 (*CurrentEntry) += 1;
1965 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1966 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1967 }
1968
1969 /* If we're already browsing a subnode */
1970 if (IndexRecord == NULL)
1971 {
1972 return STATUS_OBJECT_PATH_NOT_FOUND;
1973 }
1974
1975 /* If there's no subnode */
1976 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1977 {
1978 return STATUS_OBJECT_PATH_NOT_FOUND;
1979 }
1980
1981 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1982 if (!NT_SUCCESS(Status))
1983 {
1984 DPRINT("Corrupted filesystem!\n");
1985 return Status;
1986 }
1987
1988 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1989 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1990 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1991 {
1992 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1993 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1994 if (!NT_SUCCESS(Status))
1995 {
1996 break;
1997 }
1998
1999 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
2000 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
2001 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
2002 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
2003 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
2004 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
2005
2006 Status = BrowseIndexEntries(NULL,
2007 NULL,
2008 NULL,
2009 0,
2010 FirstEntry,
2011 LastEntry,
2012 FileName,
2013 StartEntry,
2014 CurrentEntry,
2015 DirSearch,
2016 CaseSensitive,
2017 OutMFTIndex);
2018 if (NT_SUCCESS(Status))
2019 {
2020 break;
2021 }
2022 }
2023
2024 ReleaseAttributeContext(IndexAllocationCtx);
2025 return Status;
2026 }
2027
2028 NTSTATUS
2029 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
2030 ULONGLONG MFTIndex,
2031 PUNICODE_STRING FileName,
2032 PULONG FirstEntry,
2033 BOOLEAN DirSearch,
2034 ULONGLONG *OutMFTIndex,
2035 BOOLEAN CaseSensitive)
2036 {
2037 PFILE_RECORD_HEADER MftRecord;
2038 PNTFS_ATTR_CONTEXT IndexRootCtx;
2039 PINDEX_ROOT_ATTRIBUTE IndexRoot;
2040 PCHAR IndexRecord;
2041 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
2042 NTSTATUS Status;
2043 ULONG CurrentEntry = 0;
2044
2045 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb, MFTIndex, FileName, *FirstEntry, DirSearch, OutMFTIndex);
2046
2047 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
2048 Vcb->NtfsInfo.BytesPerFileRecord,
2049 TAG_NTFS);
2050 if (MftRecord == NULL)
2051 {
2052 return STATUS_INSUFFICIENT_RESOURCES;
2053 }
2054
2055 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
2056 if (!NT_SUCCESS(Status))
2057 {
2058 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2059 return Status;
2060 }
2061
2062 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
2063 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
2064 if (!NT_SUCCESS(Status))
2065 {
2066 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2067 return Status;
2068 }
2069
2070 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
2071 if (IndexRecord == NULL)
2072 {
2073 ReleaseAttributeContext(IndexRootCtx);
2074 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2075 return STATUS_INSUFFICIENT_RESOURCES;
2076 }
2077
2078 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
2079 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
2080 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
2081 /* Index root is always resident. */
2082 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
2083 ReleaseAttributeContext(IndexRootCtx);
2084
2085 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
2086
2087 Status = BrowseIndexEntries(Vcb,
2088 MftRecord,
2089 IndexRecord,
2090 IndexRoot->SizeOfEntry,
2091 IndexEntry,
2092 IndexEntryEnd,
2093 FileName,
2094 FirstEntry,
2095 &CurrentEntry,
2096 DirSearch,
2097 CaseSensitive,
2098 OutMFTIndex);
2099
2100 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2101 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2102
2103 return Status;
2104 }
2105
2106 NTSTATUS
2107 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
2108 PUNICODE_STRING PathName,
2109 BOOLEAN CaseSensitive,
2110 PFILE_RECORD_HEADER *FileRecord,
2111 PULONGLONG MFTIndex,
2112 ULONGLONG CurrentMFTIndex)
2113 {
2114 UNICODE_STRING Current, Remaining;
2115 NTSTATUS Status;
2116 ULONG FirstEntry = 0;
2117
2118 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
2119 Vcb,
2120 PathName,
2121 CaseSensitive ? "TRUE" : "FALSE",
2122 FileRecord,
2123 MFTIndex,
2124 CurrentMFTIndex);
2125
2126 FsRtlDissectName(*PathName, &Current, &Remaining);
2127
2128 while (Current.Length != 0)
2129 {
2130 DPRINT("Current: %wZ\n", &Current);
2131
2132 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex, CaseSensitive);
2133 if (!NT_SUCCESS(Status))
2134 {
2135 return Status;
2136 }
2137
2138 if (Remaining.Length == 0)
2139 break;
2140
2141 FsRtlDissectName(Current, &Current, &Remaining);
2142 }
2143
2144 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2145 if (*FileRecord == NULL)
2146 {
2147 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
2148 return STATUS_INSUFFICIENT_RESOURCES;
2149 }
2150
2151 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2152 if (!NT_SUCCESS(Status))
2153 {
2154 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
2155 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2156 return Status;
2157 }
2158
2159 *MFTIndex = CurrentMFTIndex;
2160
2161 return STATUS_SUCCESS;
2162 }
2163
2164 NTSTATUS
2165 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
2166 PUNICODE_STRING PathName,
2167 BOOLEAN CaseSensitive,
2168 PFILE_RECORD_HEADER *FileRecord,
2169 PULONGLONG MFTIndex)
2170 {
2171 return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT);
2172 }
2173
2174 /**
2175 * @name NtfsDumpFileRecord
2176 * @implemented
2177 *
2178 * Provides diagnostic information about a file record. Prints a hex dump
2179 * of the entire record (based on the size reported by FileRecord->ByesInUse),
2180 * then prints a dump of each attribute.
2181 *
2182 * @param Vcb
2183 * Pointer to a DEVICE_EXTENSION describing the volume.
2184 *
2185 * @param FileRecord
2186 * Pointer to the file record to be analyzed.
2187 *
2188 * @remarks
2189 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
2190 * in size, and not just the header.
2191 *
2192 */
2193 VOID
2194 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
2195 PFILE_RECORD_HEADER FileRecord)
2196 {
2197 ULONG i, j;
2198
2199 // dump binary data, 8 bytes at a time
2200 for (i = 0; i < FileRecord->BytesInUse; i += 8)
2201 {
2202 // display current offset, in hex
2203 DbgPrint("\t%03x\t", i);
2204
2205 // display hex value of each of the next 8 bytes
2206 for (j = 0; j < 8; j++)
2207 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
2208 DbgPrint("\n");
2209 }
2210
2211 NtfsDumpFileAttributes(Vcb, FileRecord);
2212 }
2213
2214 NTSTATUS
2215 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
2216 PUNICODE_STRING SearchPattern,
2217 PULONG FirstEntry,
2218 PFILE_RECORD_HEADER *FileRecord,
2219 PULONGLONG MFTIndex,
2220 ULONGLONG CurrentMFTIndex,
2221 BOOLEAN CaseSensitive)
2222 {
2223 NTSTATUS Status;
2224
2225 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x, %s)\n",
2226 Vcb,
2227 SearchPattern,
2228 *FirstEntry,
2229 FileRecord,
2230 MFTIndex,
2231 CurrentMFTIndex,
2232 (CaseSensitive ? "TRUE" : "FALSE"));
2233
2234 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, &CurrentMFTIndex, CaseSensitive);
2235 if (!NT_SUCCESS(Status))
2236 {
2237 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
2238 return Status;
2239 }
2240
2241 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2242 if (*FileRecord == NULL)
2243 {
2244 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
2245 return STATUS_INSUFFICIENT_RESOURCES;
2246 }
2247
2248 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2249 if (!NT_SUCCESS(Status))
2250 {
2251 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
2252 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2253 return Status;
2254 }
2255
2256 *MFTIndex = CurrentMFTIndex;
2257
2258 return STATUS_SUCCESS;
2259 }
2260
2261 /* EOF */