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