[NTFS] - Add support for directory creation. Add some helper functions, some comments...
[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 PUCHAR BitmapBuffer;
1897 ULONG LengthWritten;
1898 PNTFS_ATTR_CONTEXT BitmapContext;
1899 LARGE_INTEGER BitmapBits;
1900 UCHAR SystemReservedBits;
1901
1902 DPRINT1("AddNewMftEntry(%p, %p, %p, %s)\n", FileRecord, DeviceExt, DestinationIndex, CanWait ? "TRUE" : "FALSE");
1903
1904 // First, we have to read the mft's $Bitmap attribute
1905
1906 // Find the attribute
1907 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
1908 if (!NT_SUCCESS(Status))
1909 {
1910 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1911 return Status;
1912 }
1913
1914 // Get size of bitmap
1915 BitmapDataSize = AttributeDataLength(BitmapContext->pRecord);
1916
1917 // RtlInitializeBitmap wants a ULONG-aligned pointer, and wants the memory passed to it to be a ULONG-multiple
1918 // Allocate a buffer for the $Bitmap attribute plus enough to ensure we can get a ULONG-aligned pointer
1919 BitmapBuffer = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize + sizeof(ULONG), TAG_NTFS);
1920 if (!BitmapBuffer)
1921 {
1922 ReleaseAttributeContext(BitmapContext);
1923 return STATUS_INSUFFICIENT_RESOURCES;
1924 }
1925
1926 // Get a ULONG-aligned pointer for the bitmap itself
1927 BitmapData = (PUCHAR)ALIGN_UP_BY((ULONG_PTR)BitmapBuffer, sizeof(ULONG));
1928
1929 // read $Bitmap attribute
1930 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, (PCHAR)BitmapData, BitmapDataSize);
1931
1932 if (AttrBytesRead != BitmapDataSize)
1933 {
1934 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1935 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
1936 ReleaseAttributeContext(BitmapContext);
1937 return STATUS_OBJECT_NAME_NOT_FOUND;
1938 }
1939
1940 // We need to backup the bits for records 0x10 - 0x17 (3rd byte of bitmap) and mark these records
1941 // as in-use so we don't assign files to those indices. They're reserved for the system (e.g. ChkDsk).
1942 SystemReservedBits = BitmapData[2];
1943 BitmapData[2] = 0xff;
1944
1945 // Calculate bit count
1946 BitmapBits.QuadPart = AttributeDataLength(DeviceExt->MFTContext->pRecord) /
1947 DeviceExt->NtfsInfo.BytesPerFileRecord;
1948 if (BitmapBits.HighPart != 0)
1949 {
1950 DPRINT1("\tFIXME: bitmap sizes beyond 32bits are not yet supported! (Your NTFS volume is too large)\n");
1951 NtfsGlobalData->EnableWriteSupport = FALSE;
1952 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
1953 ReleaseAttributeContext(BitmapContext);
1954 return STATUS_NOT_IMPLEMENTED;
1955 }
1956
1957 // convert buffer into bitmap
1958 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapBits.LowPart);
1959
1960 // set next available bit, preferrably after 23rd bit
1961 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
1962 if ((LONG)MftIndex == -1)
1963 {
1964 DPRINT1("Couldn't find free space in MFT for file record, increasing MFT size.\n");
1965
1966 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
1967 ReleaseAttributeContext(BitmapContext);
1968
1969 // Couldn't find a free record in the MFT, add some blank records and try again
1970 Status = IncreaseMftSize(DeviceExt, CanWait);
1971 if (!NT_SUCCESS(Status))
1972 {
1973 DPRINT1("ERROR: Couldn't find space in MFT for file or increase MFT size!\n");
1974 return Status;
1975 }
1976
1977 return AddNewMftEntry(FileRecord, DeviceExt, DestinationIndex, CanWait);
1978 }
1979
1980 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
1981
1982 // update file record with index
1983 FileRecord->MFTRecordNumber = MftIndex;
1984
1985 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1986
1987 // Restore the system reserved bits
1988 BitmapData[2] = SystemReservedBits;
1989
1990 // write the bitmap back to the MFT's $Bitmap attribute
1991 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten, FileRecord);
1992 if (!NT_SUCCESS(Status))
1993 {
1994 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1995 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
1996 ReleaseAttributeContext(BitmapContext);
1997 return Status;
1998 }
1999
2000 // update the file record (write it to disk)
2001 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
2002
2003 if (!NT_SUCCESS(Status))
2004 {
2005 DPRINT1("ERROR: Unable to write file record!\n");
2006 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2007 ReleaseAttributeContext(BitmapContext);
2008 return Status;
2009 }
2010
2011 *DestinationIndex = MftIndex;
2012
2013 ExFreePoolWithTag(BitmapBuffer, TAG_NTFS);
2014 ReleaseAttributeContext(BitmapContext);
2015
2016 return Status;
2017 }
2018
2019 /**
2020 * @name NtfsAddFilenameToDirectory
2021 * @implemented
2022 *
2023 * Adds a $FILE_NAME attribute to a given directory index.
2024 *
2025 * @param DeviceExt
2026 * Points to the target disk's DEVICE_EXTENSION.
2027 *
2028 * @param DirectoryMftIndex
2029 * Mft index of the parent directory which will receive the file.
2030 *
2031 * @param FileReferenceNumber
2032 * File reference of the file to be added to the directory. This is a combination of the
2033 * Mft index and sequence number.
2034 *
2035 * @param FilenameAttribute
2036 * Pointer to the FILENAME_ATTRIBUTE of the file being added to the directory.
2037 *
2038 * @param CaseSensitive
2039 * Boolean indicating if the function should operate in case-sensitive mode. This will be TRUE
2040 * if an application created the file with the FILE_FLAG_POSIX_SEMANTICS flag.
2041 *
2042 * @return
2043 * STATUS_SUCCESS on success.
2044 * STATUS_INSUFFICIENT_RESOURCES if an allocation fails.
2045 * STATUS_NOT_IMPLEMENTED if target address isn't at the end of the given file record.
2046 *
2047 * @remarks
2048 * WIP - Can only support a few files in a directory.
2049 * One FILENAME_ATTRIBUTE is added to the directory's index for each link to that file. So, each
2050 * file which contains one FILENAME_ATTRIBUTE for a long name and another for the 8.3 name, will
2051 * get both attributes added to its parent directory.
2052 */
2053 NTSTATUS
2054 NtfsAddFilenameToDirectory(PDEVICE_EXTENSION DeviceExt,
2055 ULONGLONG DirectoryMftIndex,
2056 ULONGLONG FileReferenceNumber,
2057 PFILENAME_ATTRIBUTE FilenameAttribute,
2058 BOOLEAN CaseSensitive)
2059 {
2060 NTSTATUS Status = STATUS_SUCCESS;
2061 PFILE_RECORD_HEADER ParentFileRecord;
2062 PNTFS_ATTR_CONTEXT IndexRootContext;
2063 PINDEX_ROOT_ATTRIBUTE I30IndexRoot;
2064 ULONG IndexRootOffset;
2065 ULONGLONG I30IndexRootLength;
2066 ULONG LengthWritten;
2067 PINDEX_ROOT_ATTRIBUTE NewIndexRoot;
2068 ULONG AttributeLength;
2069 PNTFS_ATTR_RECORD NextAttribute;
2070 PB_TREE NewTree;
2071 ULONG BtreeIndexLength;
2072 ULONG MaxIndexRootSize;
2073
2074 // Allocate memory for the parent directory
2075 ParentFileRecord = ExAllocatePoolWithTag(NonPagedPool,
2076 DeviceExt->NtfsInfo.BytesPerFileRecord,
2077 TAG_NTFS);
2078 if (!ParentFileRecord)
2079 {
2080 DPRINT1("ERROR: Couldn't allocate memory for file record!\n");
2081 return STATUS_INSUFFICIENT_RESOURCES;
2082 }
2083
2084 // Open the parent directory
2085 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2086 if (!NT_SUCCESS(Status))
2087 {
2088 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2089 DPRINT1("ERROR: Couldn't read parent directory with index %I64u\n",
2090 DirectoryMftIndex);
2091 return Status;
2092 }
2093
2094 DPRINT1("Dumping old parent file record:\n");
2095 NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
2096
2097 // Find the index root attribute for the directory
2098 Status = FindAttribute(DeviceExt,
2099 ParentFileRecord,
2100 AttributeIndexRoot,
2101 L"$I30",
2102 4,
2103 &IndexRootContext,
2104 &IndexRootOffset);
2105 if (!NT_SUCCESS(Status))
2106 {
2107 DPRINT1("ERROR: Couldn't find $I30 $INDEX_ROOT attribute for parent directory with MFT #: %I64u!\n",
2108 DirectoryMftIndex);
2109 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2110 return Status;
2111 }
2112
2113 // Find the maximum index size given what the file record can hold
2114 // First, find the max index size assuming index root is the last attribute
2115 MaxIndexRootSize = DeviceExt->NtfsInfo.BytesPerFileRecord // Start with the size of a file record
2116 - IndexRootOffset // Subtract the length of everything that comes before index root
2117 - IndexRootContext->pRecord->Resident.ValueOffset // Subtract the length of the attribute header for index root
2118 - FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header) // Subtract the length of the index header for index root
2119 - (sizeof(ULONG) * 2); // Subtract the length of the file record end marker and padding
2120
2121 // Are there attributes after this one?
2122 NextAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)ParentFileRecord + IndexRootOffset + IndexRootContext->pRecord->Length);
2123 if (NextAttribute->Type != AttributeEnd)
2124 {
2125 // Find the length of all attributes after this one, not counting the end marker
2126 ULONG LengthOfAttributes = 0;
2127 PNTFS_ATTR_RECORD CurrentAttribute = NextAttribute;
2128 while (CurrentAttribute->Type != AttributeEnd)
2129 {
2130 LengthOfAttributes += CurrentAttribute->Length;
2131 CurrentAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)CurrentAttribute + CurrentAttribute->Length);
2132 }
2133
2134 // Leave room for the existing attributes
2135 MaxIndexRootSize -= LengthOfAttributes;
2136 }
2137
2138 // Allocate memory for the index root data
2139 I30IndexRootLength = AttributeDataLength(IndexRootContext->pRecord);
2140 I30IndexRoot = ExAllocatePoolWithTag(NonPagedPool, I30IndexRootLength, TAG_NTFS);
2141 if (!I30IndexRoot)
2142 {
2143 DPRINT1("ERROR: Couldn't allocate memory for index root attribute!\n");
2144 ReleaseAttributeContext(IndexRootContext);
2145 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2146 return STATUS_INSUFFICIENT_RESOURCES;
2147 }
2148
2149 // Read the Index Root
2150 Status = ReadAttribute(DeviceExt, IndexRootContext, 0, (PCHAR)I30IndexRoot, I30IndexRootLength);
2151 if (!NT_SUCCESS(Status))
2152 {
2153 DPRINT1("ERROR: Couln't read index root attribute for Mft index #%I64u\n", DirectoryMftIndex);
2154 ReleaseAttributeContext(IndexRootContext);
2155 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2156 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2157 return Status;
2158 }
2159
2160 // Convert the index to a B*Tree
2161 Status = CreateBTreeFromIndex(DeviceExt,
2162 ParentFileRecord,
2163 IndexRootContext,
2164 I30IndexRoot,
2165 &NewTree);
2166 if (!NT_SUCCESS(Status))
2167 {
2168 DPRINT1("ERROR: Failed to create B-Tree from Index!\n");
2169 ReleaseAttributeContext(IndexRootContext);
2170 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2171 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2172 return Status;
2173 }
2174
2175 DumpBTree(NewTree);
2176
2177 // Insert the key for the file we're adding
2178 Status = NtfsInsertKey(NewTree, FileReferenceNumber, FilenameAttribute, NewTree->RootNode, CaseSensitive, MaxIndexRootSize);
2179 if (!NT_SUCCESS(Status))
2180 {
2181 DPRINT1("ERROR: Failed to insert key into B-Tree!\n");
2182 DestroyBTree(NewTree);
2183 ReleaseAttributeContext(IndexRootContext);
2184 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2185 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2186 return Status;
2187 }
2188
2189 DumpBTree(NewTree);
2190
2191 // Convert B*Tree back to Index, starting with the index allocation
2192 Status = UpdateIndexAllocation(DeviceExt, NewTree, I30IndexRoot->SizeOfEntry, ParentFileRecord);
2193 if (!NT_SUCCESS(Status))
2194 {
2195 DPRINT1("ERROR: Failed to update index allocation from B-Tree!\n");
2196 DestroyBTree(NewTree);
2197 ReleaseAttributeContext(IndexRootContext);
2198 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2199 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2200 return Status;
2201 }
2202
2203 // Create the Index Root from the B*Tree
2204 Status = CreateIndexRootFromBTree(DeviceExt, NewTree, MaxIndexRootSize, &NewIndexRoot, &BtreeIndexLength);
2205 if (!NT_SUCCESS(Status))
2206 {
2207 DPRINT1("ERROR: Failed to create Index root from B-Tree!\n");
2208 DestroyBTree(NewTree);
2209 ReleaseAttributeContext(IndexRootContext);
2210 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2211 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2212 return Status;
2213 }
2214
2215 // We're done with the B-Tree now
2216 DestroyBTree(NewTree);
2217
2218 // Write back the new index root attribute to the parent directory file record
2219
2220 // First, we need to resize the attribute.
2221 // CreateIndexRootFromBTree() should have verified that the index root fits within MaxIndexSize.
2222 // We can't set the size as we normally would, because if we extend past the file record,
2223 // we must create an index allocation and index bitmap (TODO). Also TODO: support file records with
2224 // $ATTRIBUTE_LIST's.
2225 AttributeLength = NewIndexRoot->Header.AllocatedSize + FIELD_OFFSET(INDEX_ROOT_ATTRIBUTE, Header);
2226
2227 if (AttributeLength != IndexRootContext->pRecord->Resident.ValueLength)
2228 {
2229 // Update the length of the attribute in the file record of the parent directory
2230 Status = InternalSetResidentAttributeLength(DeviceExt,
2231 IndexRootContext,
2232 ParentFileRecord,
2233 IndexRootOffset,
2234 AttributeLength);
2235 if (!NT_SUCCESS(Status))
2236 {
2237 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2238 ReleaseAttributeContext(IndexRootContext);
2239 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2240 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2241 DPRINT1("ERROR: Unable to set resident attribute length!\n");
2242 return Status;
2243 }
2244
2245 }
2246
2247 NT_ASSERT(ParentFileRecord->BytesInUse <= DeviceExt->NtfsInfo.BytesPerFileRecord);
2248
2249 Status = UpdateFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2250 if (!NT_SUCCESS(Status))
2251 {
2252 DPRINT1("ERROR: Failed to update file record of directory with index: %llx\n", DirectoryMftIndex);
2253 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2254 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2255 ReleaseAttributeContext(IndexRootContext);
2256 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2257 return Status;
2258 }
2259
2260 // Write the new index root to disk
2261 Status = WriteAttribute(DeviceExt,
2262 IndexRootContext,
2263 0,
2264 (PUCHAR)NewIndexRoot,
2265 AttributeLength,
2266 &LengthWritten,
2267 ParentFileRecord);
2268 if (!NT_SUCCESS(Status) || LengthWritten != AttributeLength)
2269 {
2270 DPRINT1("ERROR: Unable to write new index root attribute to parent directory!\n");
2271 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2272 ReleaseAttributeContext(IndexRootContext);
2273 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2274 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2275 return Status;
2276 }
2277
2278 // re-read the parent file record, so we can dump it
2279 Status = ReadFileRecord(DeviceExt, DirectoryMftIndex, ParentFileRecord);
2280 if (!NT_SUCCESS(Status))
2281 {
2282 DPRINT1("ERROR: Couldn't read parent directory after messing with it!\n");
2283 }
2284 else
2285 {
2286 DPRINT1("Dumping new parent file record:\n");
2287 NtfsDumpFileRecord(DeviceExt, ParentFileRecord);
2288 }
2289
2290 // Cleanup
2291 ExFreePoolWithTag(NewIndexRoot, TAG_NTFS);
2292 ReleaseAttributeContext(IndexRootContext);
2293 ExFreePoolWithTag(I30IndexRoot, TAG_NTFS);
2294 ExFreePoolWithTag(ParentFileRecord, TAG_NTFS);
2295
2296 return Status;
2297 }
2298
2299 NTSTATUS
2300 AddFixupArray(PDEVICE_EXTENSION Vcb,
2301 PNTFS_RECORD_HEADER Record)
2302 {
2303 USHORT *pShortToFixUp;
2304 ULONG ArrayEntryCount = Record->UsaCount - 1;
2305 ULONG Offset = Vcb->NtfsInfo.BytesPerSector - 2;
2306 ULONG i;
2307
2308 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
2309
2310 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
2311
2312 fixupArray->USN++;
2313
2314 for (i = 0; i < ArrayEntryCount; i++)
2315 {
2316 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
2317
2318 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
2319 fixupArray->Array[i] = *pShortToFixUp;
2320 *pShortToFixUp = fixupArray->USN;
2321 Offset += Vcb->NtfsInfo.BytesPerSector;
2322 }
2323
2324 return STATUS_SUCCESS;
2325 }
2326
2327 NTSTATUS
2328 ReadLCN(PDEVICE_EXTENSION Vcb,
2329 ULONGLONG lcn,
2330 ULONG count,
2331 PVOID buffer)
2332 {
2333 LARGE_INTEGER DiskSector;
2334
2335 DiskSector.QuadPart = lcn;
2336
2337 return NtfsReadSectors(Vcb->StorageDevice,
2338 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
2339 count * Vcb->NtfsInfo.SectorsPerCluster,
2340 Vcb->NtfsInfo.BytesPerSector,
2341 buffer,
2342 FALSE);
2343 }
2344
2345
2346 BOOLEAN
2347 CompareFileName(PUNICODE_STRING FileName,
2348 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
2349 BOOLEAN DirSearch,
2350 BOOLEAN CaseSensitive)
2351 {
2352 BOOLEAN Ret, Alloc = FALSE;
2353 UNICODE_STRING EntryName;
2354
2355 EntryName.Buffer = IndexEntry->FileName.Name;
2356 EntryName.Length =
2357 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
2358
2359 if (DirSearch)
2360 {
2361 UNICODE_STRING IntFileName;
2362 if (!CaseSensitive)
2363 {
2364 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
2365 Alloc = TRUE;
2366 }
2367 else
2368 {
2369 IntFileName = *FileName;
2370 }
2371
2372 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, !CaseSensitive, NULL);
2373
2374 if (Alloc)
2375 {
2376 RtlFreeUnicodeString(&IntFileName);
2377 }
2378
2379 return Ret;
2380 }
2381 else
2382 {
2383 return (RtlCompareUnicodeString(FileName, &EntryName, !CaseSensitive) == 0);
2384 }
2385 }
2386
2387 #if 0
2388 static
2389 VOID
2390 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
2391 {
2392 DPRINT1("Entry: %p\n", IndexEntry);
2393 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
2394 DPRINT1("\tLength: %u\n", IndexEntry->Length);
2395 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
2396 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
2397 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
2398 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
2399 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
2400 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
2401 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
2402 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
2403 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
2404 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
2405 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
2406 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
2407 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
2408 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
2409 }
2410 #endif
2411
2412 NTSTATUS
2413 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
2414 PFILE_RECORD_HEADER MftRecord,
2415 PCHAR IndexRecord,
2416 ULONG IndexBlockSize,
2417 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
2418 PINDEX_ENTRY_ATTRIBUTE LastEntry,
2419 PUNICODE_STRING FileName,
2420 PULONG StartEntry,
2421 PULONG CurrentEntry,
2422 BOOLEAN DirSearch,
2423 BOOLEAN CaseSensitive,
2424 ULONGLONG *OutMFTIndex)
2425 {
2426 NTSTATUS Status;
2427 ULONG RecordOffset;
2428 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
2429 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
2430 ULONGLONG IndexAllocationSize;
2431 PINDEX_BUFFER IndexBuffer;
2432
2433 DPRINT("BrowseIndexEntries(%p, %p, %p, %lu, %p, %p, %wZ, %lu, %lu, %s, %s, %p)\n",
2434 Vcb,
2435 MftRecord,
2436 IndexRecord,
2437 IndexBlockSize,
2438 FirstEntry,
2439 LastEntry,
2440 FileName,
2441 *StartEntry,
2442 *CurrentEntry,
2443 DirSearch ? "TRUE" : "FALSE",
2444 CaseSensitive ? "TRUE" : "FALSE",
2445 OutMFTIndex);
2446
2447 IndexEntry = FirstEntry;
2448 while (IndexEntry < LastEntry &&
2449 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
2450 {
2451 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) >= NTFS_FILE_FIRST_USER_FILE &&
2452 *CurrentEntry >= *StartEntry &&
2453 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
2454 CompareFileName(FileName, IndexEntry, DirSearch, CaseSensitive))
2455 {
2456 *StartEntry = *CurrentEntry;
2457 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
2458 return STATUS_SUCCESS;
2459 }
2460
2461 (*CurrentEntry) += 1;
2462 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
2463 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
2464 }
2465
2466 /* If we're already browsing a subnode */
2467 if (IndexRecord == NULL)
2468 {
2469 return STATUS_OBJECT_PATH_NOT_FOUND;
2470 }
2471
2472 /* If there's no subnode */
2473 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
2474 {
2475 return STATUS_OBJECT_PATH_NOT_FOUND;
2476 }
2477
2478 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
2479 if (!NT_SUCCESS(Status))
2480 {
2481 DPRINT1("Corrupted filesystem!\n");
2482 return Status;
2483 }
2484
2485 IndexAllocationSize = AttributeDataLength(IndexAllocationCtx->pRecord);
2486 Status = STATUS_OBJECT_PATH_NOT_FOUND;
2487 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
2488 {
2489 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
2490 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
2491 if (!NT_SUCCESS(Status))
2492 {
2493 break;
2494 }
2495
2496 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
2497 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
2498 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
2499 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
2500 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
2501 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
2502
2503 Status = BrowseIndexEntries(NULL,
2504 NULL,
2505 NULL,
2506 0,
2507 FirstEntry,
2508 LastEntry,
2509 FileName,
2510 StartEntry,
2511 CurrentEntry,
2512 DirSearch,
2513 CaseSensitive,
2514 OutMFTIndex);
2515 if (NT_SUCCESS(Status))
2516 {
2517 break;
2518 }
2519 }
2520
2521 ReleaseAttributeContext(IndexAllocationCtx);
2522 return Status;
2523 }
2524
2525 NTSTATUS
2526 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
2527 ULONGLONG MFTIndex,
2528 PUNICODE_STRING FileName,
2529 PULONG FirstEntry,
2530 BOOLEAN DirSearch,
2531 BOOLEAN CaseSensitive,
2532 ULONGLONG *OutMFTIndex)
2533 {
2534 PFILE_RECORD_HEADER MftRecord;
2535 PNTFS_ATTR_CONTEXT IndexRootCtx;
2536 PINDEX_ROOT_ATTRIBUTE IndexRoot;
2537 PCHAR IndexRecord;
2538 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
2539 NTSTATUS Status;
2540 ULONG CurrentEntry = 0;
2541
2542 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %lu, %s, %s, %p)\n",
2543 Vcb,
2544 MFTIndex,
2545 FileName,
2546 *FirstEntry,
2547 DirSearch ? "TRUE" : "FALSE",
2548 CaseSensitive ? "TRUE" : "FALSE",
2549 OutMFTIndex);
2550
2551 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
2552 Vcb->NtfsInfo.BytesPerFileRecord,
2553 TAG_NTFS);
2554 if (MftRecord == NULL)
2555 {
2556 return STATUS_INSUFFICIENT_RESOURCES;
2557 }
2558
2559 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
2560 if (!NT_SUCCESS(Status))
2561 {
2562 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2563 return Status;
2564 }
2565
2566 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
2567 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
2568 if (!NT_SUCCESS(Status))
2569 {
2570 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2571 return Status;
2572 }
2573
2574 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
2575 if (IndexRecord == NULL)
2576 {
2577 ReleaseAttributeContext(IndexRootCtx);
2578 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2579 return STATUS_INSUFFICIENT_RESOURCES;
2580 }
2581
2582 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
2583 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
2584 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
2585 /* Index root is always resident. */
2586 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
2587 ReleaseAttributeContext(IndexRootCtx);
2588
2589 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
2590
2591 Status = BrowseIndexEntries(Vcb,
2592 MftRecord,
2593 IndexRecord,
2594 IndexRoot->SizeOfEntry,
2595 IndexEntry,
2596 IndexEntryEnd,
2597 FileName,
2598 FirstEntry,
2599 &CurrentEntry,
2600 DirSearch,
2601 CaseSensitive,
2602 OutMFTIndex);
2603
2604 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
2605 ExFreePoolWithTag(MftRecord, TAG_NTFS);
2606
2607 return Status;
2608 }
2609
2610 NTSTATUS
2611 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
2612 PUNICODE_STRING PathName,
2613 BOOLEAN CaseSensitive,
2614 PFILE_RECORD_HEADER *FileRecord,
2615 PULONGLONG MFTIndex,
2616 ULONGLONG CurrentMFTIndex)
2617 {
2618 UNICODE_STRING Current, Remaining;
2619 NTSTATUS Status;
2620 ULONG FirstEntry = 0;
2621
2622 DPRINT("NtfsLookupFileAt(%p, %wZ, %s, %p, %p, %I64x)\n",
2623 Vcb,
2624 PathName,
2625 CaseSensitive ? "TRUE" : "FALSE",
2626 FileRecord,
2627 MFTIndex,
2628 CurrentMFTIndex);
2629
2630 FsRtlDissectName(*PathName, &Current, &Remaining);
2631
2632 while (Current.Length != 0)
2633 {
2634 DPRINT("Current: %wZ\n", &Current);
2635
2636 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, CaseSensitive, &CurrentMFTIndex);
2637 if (!NT_SUCCESS(Status))
2638 {
2639 return Status;
2640 }
2641
2642 if (Remaining.Length == 0)
2643 break;
2644
2645 FsRtlDissectName(Current, &Current, &Remaining);
2646 }
2647
2648 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2649 if (*FileRecord == NULL)
2650 {
2651 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
2652 return STATUS_INSUFFICIENT_RESOURCES;
2653 }
2654
2655 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2656 if (!NT_SUCCESS(Status))
2657 {
2658 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
2659 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2660 return Status;
2661 }
2662
2663 *MFTIndex = CurrentMFTIndex;
2664
2665 return STATUS_SUCCESS;
2666 }
2667
2668 NTSTATUS
2669 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
2670 PUNICODE_STRING PathName,
2671 BOOLEAN CaseSensitive,
2672 PFILE_RECORD_HEADER *FileRecord,
2673 PULONGLONG MFTIndex)
2674 {
2675 return NtfsLookupFileAt(Vcb, PathName, CaseSensitive, FileRecord, MFTIndex, NTFS_FILE_ROOT);
2676 }
2677
2678 /**
2679 * @name NtfsDumpFileRecord
2680 * @implemented
2681 *
2682 * Provides diagnostic information about a file record. Prints a hex dump
2683 * of the entire record (based on the size reported by FileRecord->ByesInUse),
2684 * then prints a dump of each attribute.
2685 *
2686 * @param Vcb
2687 * Pointer to a DEVICE_EXTENSION describing the volume.
2688 *
2689 * @param FileRecord
2690 * Pointer to the file record to be analyzed.
2691 *
2692 * @remarks
2693 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
2694 * in size, and not just the header.
2695 *
2696 */
2697 VOID
2698 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
2699 PFILE_RECORD_HEADER FileRecord)
2700 {
2701 ULONG i, j;
2702
2703 // dump binary data, 8 bytes at a time
2704 for (i = 0; i < FileRecord->BytesInUse; i += 8)
2705 {
2706 // display current offset, in hex
2707 DbgPrint("\t%03x\t", i);
2708
2709 // display hex value of each of the next 8 bytes
2710 for (j = 0; j < 8; j++)
2711 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
2712 DbgPrint("\n");
2713 }
2714
2715 NtfsDumpFileAttributes(Vcb, FileRecord);
2716 }
2717
2718 NTSTATUS
2719 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
2720 PUNICODE_STRING SearchPattern,
2721 PULONG FirstEntry,
2722 PFILE_RECORD_HEADER *FileRecord,
2723 PULONGLONG MFTIndex,
2724 ULONGLONG CurrentMFTIndex,
2725 BOOLEAN CaseSensitive)
2726 {
2727 NTSTATUS Status;
2728
2729 DPRINT("NtfsFindFileAt(%p, %wZ, %lu, %p, %p, %I64x, %s)\n",
2730 Vcb,
2731 SearchPattern,
2732 *FirstEntry,
2733 FileRecord,
2734 MFTIndex,
2735 CurrentMFTIndex,
2736 (CaseSensitive ? "TRUE" : "FALSE"));
2737
2738 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, CaseSensitive, &CurrentMFTIndex);
2739 if (!NT_SUCCESS(Status))
2740 {
2741 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
2742 return Status;
2743 }
2744
2745 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
2746 if (*FileRecord == NULL)
2747 {
2748 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
2749 return STATUS_INSUFFICIENT_RESOURCES;
2750 }
2751
2752 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
2753 if (!NT_SUCCESS(Status))
2754 {
2755 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
2756 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
2757 return Status;
2758 }
2759
2760 *MFTIndex = CurrentMFTIndex;
2761
2762 return STATUS_SUCCESS;
2763 }
2764
2765 /* EOF */