19ae0d15c177f901de68ef09e2ebbadb619724de
[reactos.git] / drivers / filesystems / ntfs / mft.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 2002, 2014 ReactOS Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 * COPYRIGHT: See COPYING in the top level directory
20 * PROJECT: ReactOS kernel
21 * FILE: drivers/filesystem/ntfs/mft.c
22 * PURPOSE: NTFS filesystem driver
23 * PROGRAMMERS: Eric Kohl
24 * Valentin Verkhovsky
25 * Pierre Schweitzer (pierre@reactos.org)
26 * Hervé Poussineau (hpoussin@reactos.org)
27 * Trevor Thompson
28 */
29
30 /* INCLUDES *****************************************************************/
31
32 #include "ntfs.h"
33
34 #define NDEBUG
35 #include <debug.h>
36
37 /* FUNCTIONS ****************************************************************/
38
39 PNTFS_ATTR_CONTEXT
40 PrepareAttributeContext(PNTFS_ATTR_RECORD AttrRecord)
41 {
42 PNTFS_ATTR_CONTEXT Context;
43
44 Context = ExAllocatePoolWithTag(NonPagedPool,
45 FIELD_OFFSET(NTFS_ATTR_CONTEXT, Record) + AttrRecord->Length,
46 TAG_NTFS);
47 RtlCopyMemory(&Context->Record, AttrRecord, AttrRecord->Length);
48 if (AttrRecord->IsNonResident)
49 {
50 LONGLONG DataRunOffset;
51 ULONGLONG DataRunLength;
52 ULONGLONG NextVBN = 0;
53 PUCHAR DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
54
55 Context->CacheRun = DataRun;
56 Context->CacheRunOffset = 0;
57 Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
58 Context->CacheRunLength = DataRunLength;
59 if (DataRunOffset != -1)
60 {
61 /* Normal run. */
62 Context->CacheRunStartLCN =
63 Context->CacheRunLastLCN = DataRunOffset;
64 }
65 else
66 {
67 /* Sparse run. */
68 Context->CacheRunStartLCN = -1;
69 Context->CacheRunLastLCN = 0;
70 }
71 Context->CacheRunCurrentOffset = 0;
72
73 // Convert the data runs to a map control block
74 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN)))
75 {
76 DPRINT1("Unable to convert data runs to MCB!\n");
77 ExFreePoolWithTag(Context, TAG_NTFS);
78 return NULL;
79 }
80 }
81
82 return Context;
83 }
84
85
86 VOID
87 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
88 {
89 if (Context->Record.IsNonResident)
90 {
91 FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
92 }
93
94 ExFreePoolWithTag(Context, TAG_NTFS);
95 }
96
97
98 /**
99 * @name FindAttribute
100 * @implemented
101 *
102 * Searches a file record for an attribute matching the given type and name.
103 *
104 * @param Offset
105 * Optional pointer to a ULONG that will receive the offset of the found attribute
106 * from the beginning of the record. Can be set to NULL.
107 */
108 NTSTATUS
109 FindAttribute(PDEVICE_EXTENSION Vcb,
110 PFILE_RECORD_HEADER MftRecord,
111 ULONG Type,
112 PCWSTR Name,
113 ULONG NameLength,
114 PNTFS_ATTR_CONTEXT * AttrCtx,
115 PULONG Offset)
116 {
117 BOOLEAN Found;
118 NTSTATUS Status;
119 FIND_ATTR_CONTXT Context;
120 PNTFS_ATTR_RECORD Attribute;
121
122 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %u, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx);
123
124 Found = FALSE;
125 Status = FindFirstAttribute(&Context, Vcb, MftRecord, FALSE, &Attribute);
126 while (NT_SUCCESS(Status))
127 {
128 if (Attribute->Type == Type && Attribute->NameLength == NameLength)
129 {
130 if (NameLength != 0)
131 {
132 PWCHAR AttrName;
133
134 AttrName = (PWCHAR)((PCHAR)Attribute + Attribute->NameOffset);
135 DPRINT("%.*S, %.*S\n", Attribute->NameLength, AttrName, NameLength, Name);
136 if (RtlCompareMemory(AttrName, Name, NameLength << 1) == (NameLength << 1))
137 {
138 Found = TRUE;
139 }
140 }
141 else
142 {
143 Found = TRUE;
144 }
145
146 if (Found)
147 {
148 /* Found it, fill up the context and return. */
149 DPRINT("Found context\n");
150 *AttrCtx = PrepareAttributeContext(Attribute);
151
152 (*AttrCtx)->FileMFTIndex = MftRecord->MFTRecordNumber;
153
154 if (Offset != NULL)
155 *Offset = Context.Offset;
156
157 FindCloseAttribute(&Context);
158 return STATUS_SUCCESS;
159 }
160 }
161
162 Status = FindNextAttribute(&Context, &Attribute);
163 }
164
165 FindCloseAttribute(&Context);
166 return STATUS_OBJECT_NAME_NOT_FOUND;
167 }
168
169
170 ULONGLONG
171 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord)
172 {
173 if (AttrRecord->IsNonResident)
174 return AttrRecord->NonResident.AllocatedSize;
175 else
176 return AttrRecord->Resident.ValueLength;
177 }
178
179
180 ULONGLONG
181 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
182 {
183 if (AttrRecord->IsNonResident)
184 return AttrRecord->NonResident.DataSize;
185 else
186 return AttrRecord->Resident.ValueLength;
187 }
188
189 VOID
190 InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
191 PFILE_RECORD_HEADER FileRecord,
192 ULONG AttrOffset,
193 ULONG DataSize)
194 {
195 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
196 ULONG NextAttributeOffset;
197
198 DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize);
199
200 // update ValueLength Field
201 AttrContext->Record.Resident.ValueLength =
202 Destination->Resident.ValueLength = DataSize;
203
204 // calculate the record length and end marker offset
205 AttrContext->Record.Length =
206 Destination->Length = DataSize + AttrContext->Record.Resident.ValueOffset;
207 NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
208
209 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
210 if (NextAttributeOffset % 8 != 0)
211 {
212 USHORT Padding = 8 - (NextAttributeOffset % 8);
213 NextAttributeOffset += Padding;
214 AttrContext->Record.Length += Padding;
215 Destination->Length += Padding;
216 }
217
218 // advance Destination to the final "attribute" and set the file record end
219 Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
220 SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END);
221 }
222
223 /**
224 * @parameter FileRecord
225 * Pointer to a file record. Must be a full record at least
226 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
227 */
228 NTSTATUS
229 SetAttributeDataLength(PFILE_OBJECT FileObject,
230 PNTFS_FCB Fcb,
231 PNTFS_ATTR_CONTEXT AttrContext,
232 ULONG AttrOffset,
233 PFILE_RECORD_HEADER FileRecord,
234 PLARGE_INTEGER DataSize)
235 {
236 NTSTATUS Status = STATUS_SUCCESS;
237 ULONG BytesPerCluster = Fcb->Vcb->NtfsInfo.BytesPerCluster;
238
239 // are we truncating the file?
240 if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record))
241 {
242 if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize))
243 {
244 DPRINT1("Can't truncate a memory-mapped file!\n");
245 return STATUS_USER_MAPPED_FILE;
246 }
247 }
248
249 if (AttrContext->Record.IsNonResident)
250 {
251 ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster);
252 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
253 ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster;
254
255 // do we need to increase the allocation size?
256 if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize)
257 {
258 ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters;
259 LARGE_INTEGER LastClusterInDataRun;
260 ULONG NextAssignedCluster;
261 ULONG AssignedClusters;
262
263 if (ExistingClusters == 0)
264 {
265 LastClusterInDataRun.QuadPart = 0;
266 }
267 else
268 {
269 if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
270 (LONGLONG)AttrContext->Record.NonResident.HighestVCN,
271 (PLONGLONG)&LastClusterInDataRun.QuadPart,
272 NULL,
273 NULL,
274 NULL,
275 NULL))
276 {
277 DPRINT1("Error looking up final large MCB entry!\n");
278
279 // Most likely, HighestVCN went above the largest mapping
280 DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
281 return STATUS_INVALID_PARAMETER;
282 }
283 }
284
285 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
286 DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
287
288 while (ClustersNeeded > 0)
289 {
290 Status = NtfsAllocateClusters(Fcb->Vcb,
291 LastClusterInDataRun.LowPart + 1,
292 ClustersNeeded,
293 &NextAssignedCluster,
294 &AssignedClusters);
295
296 if (!NT_SUCCESS(Status))
297 {
298 DPRINT1("Error: Unable to allocate requested clusters!\n");
299 return Status;
300 }
301
302 // now we need to add the clusters we allocated to the data run
303 Status = AddRun(Fcb->Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters);
304 if (!NT_SUCCESS(Status))
305 {
306 DPRINT1("Error: Unable to add data run!\n");
307 return Status;
308 }
309
310 ClustersNeeded -= AssignedClusters;
311 LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1;
312 }
313 }
314 else if (AttrContext->Record.NonResident.AllocatedSize > AllocationSize)
315 {
316 // shrink allocation size
317 ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster);
318 Status = FreeClusters(Fcb->Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree);
319 }
320
321 // TODO: is the file compressed, encrypted, or sparse?
322
323 // NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource
324
325 Fcb->RFCB.AllocationSize.QuadPart = AllocationSize;
326 AttrContext->Record.NonResident.AllocatedSize = AllocationSize;
327 AttrContext->Record.NonResident.DataSize = DataSize->QuadPart;
328 AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
329
330 DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
331 DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
332 DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
333
334 DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
335 }
336 else
337 {
338 // resident attribute
339
340 // find the next attribute
341 ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
342 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
343
344 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
345
346 // Do we need to increase the data length?
347 if (DataSize->QuadPart > AttrContext->Record.Resident.ValueLength)
348 {
349 // There's usually padding at the end of a record. Do we need to extend past it?
350 ULONG MaxValueLength = AttrContext->Record.Length - AttrContext->Record.Resident.ValueOffset;
351 if (MaxValueLength < DataSize->LowPart)
352 {
353 // If this is the last attribute, we could move the end marker to the very end of the file record
354 MaxValueLength += Fcb->Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
355
356 if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
357 {
358 // convert attribute to non-resident
359 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
360 LARGE_INTEGER AttribDataSize;
361 PVOID AttribData;
362 ULONG EndAttributeOffset;
363 ULONG LengthWritten;
364
365 DPRINT1("Converting attribute to non-resident.\n");
366
367 AttribDataSize.QuadPart = AttrContext->Record.Resident.ValueLength;
368
369 // Is there existing data we need to back-up?
370 if (AttribDataSize.QuadPart > 0)
371 {
372 AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
373 if (AttribData == NULL)
374 {
375 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
376 return STATUS_INSUFFICIENT_RESOURCES;
377 }
378
379 // read data to temp buffer
380 Status = ReadAttribute(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
381 if (!NT_SUCCESS(Status))
382 {
383 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
384 ExFreePoolWithTag(AttribData, TAG_NTFS);
385 return Status;
386 }
387 }
388
389 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
390
391 // Zero out the NonResident structure
392 RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN,
393 FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
394 RtlZeroMemory(&Destination->NonResident.LowestVCN,
395 FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
396
397 // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
398 AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2);
399
400 // mark the attribute as non-resident
401 AttrContext->Record.IsNonResident = Destination->IsNonResident = 1;
402
403 // update the end of the file record
404 // calculate position of end markers (1 byte for empty data run)
405 EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1;
406 EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, 8);
407
408 // Update the length
409 Destination->Length = EndAttributeOffset - AttrOffset;
410 AttrContext->Record.Length = Destination->Length;
411
412 // Update the file record end
413 SetFileRecordEnd(FileRecord,
414 (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset),
415 FILE_RECORD_END);
416
417 // update file record on disk
418 Status = UpdateFileRecord(Fcb->Vcb, AttrContext->FileMFTIndex, FileRecord);
419 if (!NT_SUCCESS(Status))
420 {
421 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
422 if (AttribDataSize.QuadPart > 0)
423 ExFreePoolWithTag(AttribData, TAG_NTFS);
424 return Status;
425 }
426
427 // Initialize the MCB, potentially catch an exception
428 _SEH2_TRY{
429 FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
430 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
431 _SEH2_YIELD(return _SEH2_GetExceptionCode());
432 } _SEH2_END;
433
434 // Now we can treat the attribute as non-resident and enlarge it normally
435 Status = SetAttributeDataLength(FileObject, Fcb, AttrContext, AttrOffset, FileRecord, DataSize);
436 if (!NT_SUCCESS(Status))
437 {
438 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
439 if (AttribDataSize.QuadPart > 0)
440 ExFreePoolWithTag(AttribData, TAG_NTFS);
441 return Status;
442 }
443
444 // restore the back-up attribute, if we made one
445 if (AttribDataSize.QuadPart > 0)
446 {
447 Status = WriteAttribute(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
448 if (!NT_SUCCESS(Status))
449 {
450 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
451 // TODO: Reverse migration so no data is lost
452 ExFreePoolWithTag(AttribData, TAG_NTFS);
453 return Status;
454 }
455
456 ExFreePoolWithTag(AttribData, TAG_NTFS);
457 }
458 }
459 }
460 }
461 else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength)
462 {
463 // we need to decrease the length
464 if (NextAttribute->Type != AttributeEnd)
465 {
466 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
467 return STATUS_NOT_IMPLEMENTED;
468 }
469 }
470
471 // set the new length of the resident attribute (if we didn't migrate it)
472 if(!AttrContext->Record.IsNonResident)
473 InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
474 }
475
476 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
477
478 // write the updated file record back to disk
479 Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord);
480
481 if (NT_SUCCESS(Status))
482 {
483 Fcb->RFCB.FileSize = *DataSize;
484 Fcb->RFCB.ValidDataLength = *DataSize;
485 CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
486 }
487
488 return STATUS_SUCCESS;
489 }
490
491 /**
492 * @name SetFileRecordEnd
493 * @implemented
494 *
495 * This small function sets a new endpoint for the file record. It set's the final
496 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
497 *
498 * @param FileRecord
499 * Pointer to the file record whose endpoint (length) will be set.
500 *
501 * @param AttrEnd
502 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
503 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
504 *
505 * @param EndMarker
506 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
507 * a file record, it preserves the final ULONG that previously ended the record, even though this
508 * value is (to my knowledge) never used. We emulate this behavior.
509 *
510 */
511 VOID
512 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
513 PNTFS_ATTR_RECORD AttrEnd,
514 ULONG EndMarker)
515 {
516 // mark the end of attributes
517 AttrEnd->Type = AttributeEnd;
518
519 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
520 AttrEnd->Length = EndMarker;
521
522 // recalculate bytes in use
523 FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2;
524 }
525
526 ULONG
527 ReadAttribute(PDEVICE_EXTENSION Vcb,
528 PNTFS_ATTR_CONTEXT Context,
529 ULONGLONG Offset,
530 PCHAR Buffer,
531 ULONG Length)
532 {
533 ULONGLONG LastLCN;
534 PUCHAR DataRun;
535 LONGLONG DataRunOffset;
536 ULONGLONG DataRunLength;
537 LONGLONG DataRunStartLCN;
538 ULONGLONG CurrentOffset;
539 ULONG ReadLength;
540 ULONG AlreadyRead;
541 NTSTATUS Status;
542
543 //TEMPTEMP
544 PUCHAR TempBuffer;
545
546 if (!Context->Record.IsNonResident)
547 {
548 if (Offset > Context->Record.Resident.ValueLength)
549 return 0;
550 if (Offset + Length > Context->Record.Resident.ValueLength)
551 Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
552 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
553 return Length;
554 }
555
556 /*
557 * Non-resident attribute
558 */
559
560 /*
561 * I. Find the corresponding start data run.
562 */
563
564 AlreadyRead = 0;
565
566 // FIXME: Cache seems to be non-working. Disable it for now
567 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
568 if (0)
569 {
570 DataRun = Context->CacheRun;
571 LastLCN = Context->CacheRunLastLCN;
572 DataRunStartLCN = Context->CacheRunStartLCN;
573 DataRunLength = Context->CacheRunLength;
574 CurrentOffset = Context->CacheRunCurrentOffset;
575 }
576 else
577 {
578 //TEMPTEMP
579 ULONG UsedBufferSize;
580 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
581
582 LastLCN = 0;
583 CurrentOffset = 0;
584
585 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
586 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
587 TempBuffer,
588 Vcb->NtfsInfo.BytesPerFileRecord,
589 &UsedBufferSize);
590
591 DataRun = TempBuffer;
592
593 while (1)
594 {
595 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
596 if (DataRunOffset != -1)
597 {
598 /* Normal data run. */
599 DataRunStartLCN = LastLCN + DataRunOffset;
600 LastLCN = DataRunStartLCN;
601 }
602 else
603 {
604 /* Sparse data run. */
605 DataRunStartLCN = -1;
606 }
607
608 if (Offset >= CurrentOffset &&
609 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
610 {
611 break;
612 }
613
614 if (*DataRun == 0)
615 {
616 return AlreadyRead;
617 }
618
619 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
620 }
621 }
622
623 /*
624 * II. Go through the run list and read the data
625 */
626
627 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
628 if (DataRunStartLCN == -1)
629 {
630 RtlZeroMemory(Buffer, ReadLength);
631 Status = STATUS_SUCCESS;
632 }
633 else
634 {
635 Status = NtfsReadDisk(Vcb->StorageDevice,
636 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
637 ReadLength,
638 Vcb->NtfsInfo.BytesPerSector,
639 (PVOID)Buffer,
640 FALSE);
641 }
642 if (NT_SUCCESS(Status))
643 {
644 Length -= ReadLength;
645 Buffer += ReadLength;
646 AlreadyRead += ReadLength;
647
648 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
649 {
650 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
651 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
652 if (DataRunOffset != (ULONGLONG)-1)
653 {
654 DataRunStartLCN = LastLCN + DataRunOffset;
655 LastLCN = DataRunStartLCN;
656 }
657 else
658 DataRunStartLCN = -1;
659 }
660
661 while (Length > 0)
662 {
663 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
664 if (DataRunStartLCN == -1)
665 RtlZeroMemory(Buffer, ReadLength);
666 else
667 {
668 Status = NtfsReadDisk(Vcb->StorageDevice,
669 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
670 ReadLength,
671 Vcb->NtfsInfo.BytesPerSector,
672 (PVOID)Buffer,
673 FALSE);
674 if (!NT_SUCCESS(Status))
675 break;
676 }
677
678 Length -= ReadLength;
679 Buffer += ReadLength;
680 AlreadyRead += ReadLength;
681
682 /* We finished this request, but there still data in this data run. */
683 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
684 break;
685
686 /*
687 * Go to next run in the list.
688 */
689
690 if (*DataRun == 0)
691 break;
692 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
693 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
694 if (DataRunOffset != -1)
695 {
696 /* Normal data run. */
697 DataRunStartLCN = LastLCN + DataRunOffset;
698 LastLCN = DataRunStartLCN;
699 }
700 else
701 {
702 /* Sparse data run. */
703 DataRunStartLCN = -1;
704 }
705 } /* while */
706
707 } /* if Disk */
708
709 // TEMPTEMP
710 if (Context->Record.IsNonResident)
711 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
712
713 Context->CacheRun = DataRun;
714 Context->CacheRunOffset = Offset + AlreadyRead;
715 Context->CacheRunStartLCN = DataRunStartLCN;
716 Context->CacheRunLength = DataRunLength;
717 Context->CacheRunLastLCN = LastLCN;
718 Context->CacheRunCurrentOffset = CurrentOffset;
719
720 return AlreadyRead;
721 }
722
723
724 /**
725 * @name WriteAttribute
726 * @implemented
727 *
728 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
729 * and it still needs more documentation / cleaning up.
730 *
731 * @param Vcb
732 * Volume Control Block indicating which volume to write the attribute to
733 *
734 * @param Context
735 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
736 *
737 * @param Offset
738 * Offset, in bytes, from the beginning of the attribute indicating where to start
739 * writing data
740 *
741 * @param Buffer
742 * The data that's being written to the device
743 *
744 * @param Length
745 * How much data will be written, in bytes
746 *
747 * @param RealLengthWritten
748 * Pointer to a ULONG which will receive how much data was written, in bytes
749 *
750 * @return
751 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
752 * writing to a sparse file.
753 *
754 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
755 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
756 *
757 */
758
759 NTSTATUS
760 WriteAttribute(PDEVICE_EXTENSION Vcb,
761 PNTFS_ATTR_CONTEXT Context,
762 ULONGLONG Offset,
763 const PUCHAR Buffer,
764 ULONG Length,
765 PULONG RealLengthWritten)
766 {
767 ULONGLONG LastLCN;
768 PUCHAR DataRun;
769 LONGLONG DataRunOffset;
770 ULONGLONG DataRunLength;
771 LONGLONG DataRunStartLCN;
772 ULONGLONG CurrentOffset;
773 ULONG WriteLength;
774 NTSTATUS Status;
775 PUCHAR SourceBuffer = Buffer;
776 LONGLONG StartingOffset;
777
778 //TEMPTEMP
779 PUCHAR TempBuffer;
780
781
782 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten);
783
784 *RealLengthWritten = 0;
785
786 // is this a resident attribute?
787 if (!Context->Record.IsNonResident)
788 {
789 ULONG AttributeOffset;
790 PNTFS_ATTR_CONTEXT FoundContext;
791 PFILE_RECORD_HEADER FileRecord;
792
793 if (Offset + Length > Context->Record.Resident.ValueLength)
794 {
795 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
796 return STATUS_INVALID_PARAMETER;
797 }
798
799 FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
800
801 if (!FileRecord)
802 {
803 DPRINT1("Error: Couldn't allocate file record!\n");
804 return STATUS_NO_MEMORY;
805 }
806
807 // read the file record
808 ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
809
810 // find where to write the attribute data to
811 Status = FindAttribute(Vcb, FileRecord,
812 Context->Record.Type,
813 (PCWSTR)((PCHAR)&Context->Record + Context->Record.NameOffset),
814 Context->Record.NameLength,
815 &FoundContext,
816 &AttributeOffset);
817
818 if (!NT_SUCCESS(Status))
819 {
820 DPRINT1("ERROR: Couldn't find matching attribute!\n");
821 ExFreePoolWithTag(FileRecord, TAG_NTFS);
822 return Status;
823 }
824
825 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->Record.Resident.ValueLength);
826 Offset += AttributeOffset + Context->Record.Resident.ValueOffset;
827
828 if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord)
829 {
830 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
831 ReleaseAttributeContext(FoundContext);
832 ExFreePoolWithTag(FileRecord, TAG_NTFS);
833 return STATUS_INVALID_PARAMETER;
834 }
835
836 // copy the data being written into the file record
837 RtlCopyMemory((PCHAR)FileRecord + Offset, Buffer, Length);
838
839 Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
840
841 ReleaseAttributeContext(FoundContext);
842 ExFreePoolWithTag(FileRecord, TAG_NTFS);
843
844 if (NT_SUCCESS(Status))
845 *RealLengthWritten = Length;
846
847 return Status;
848 }
849
850 // This is a non-resident attribute.
851
852 // I. Find the corresponding start data run.
853
854 // FIXME: Cache seems to be non-working. Disable it for now
855 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
856 /*if (0)
857 {
858 DataRun = Context->CacheRun;
859 LastLCN = Context->CacheRunLastLCN;
860 DataRunStartLCN = Context->CacheRunStartLCN;
861 DataRunLength = Context->CacheRunLength;
862 CurrentOffset = Context->CacheRunCurrentOffset;
863 }
864 else*/
865 {
866 ULONG UsedBufferSize;
867 LastLCN = 0;
868 CurrentOffset = 0;
869
870 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
871 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
872
873 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
874 TempBuffer,
875 Vcb->NtfsInfo.BytesPerFileRecord,
876 &UsedBufferSize);
877
878 DataRun = TempBuffer;
879
880 while (1)
881 {
882 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
883 if (DataRunOffset != -1)
884 {
885 // Normal data run.
886 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
887 DataRunStartLCN = LastLCN + DataRunOffset;
888 LastLCN = DataRunStartLCN;
889 }
890 else
891 {
892 // Sparse data run. We can't support writing to sparse files yet
893 // (it may require increasing the allocation size).
894 DataRunStartLCN = -1;
895 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
896 return STATUS_NOT_IMPLEMENTED;
897 }
898
899 // Have we reached the data run we're trying to write to?
900 if (Offset >= CurrentOffset &&
901 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
902 {
903 break;
904 }
905
906 if (*DataRun == 0)
907 {
908 // We reached the last assigned cluster
909 // TODO: assign new clusters to the end of the file.
910 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
911 // [We can reach here by creating a new file record when the MFT isn't large enough]
912 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
913 return STATUS_END_OF_FILE;
914 }
915
916 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
917 }
918 }
919
920 // II. Go through the run list and write the data
921
922 /* REVIEWME -- As adapted from NtfsReadAttribute():
923 We seem to be making a special case for the first applicable data run, but I'm not sure why.
924 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
925
926 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
927
928 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
929
930 // Write the data to the disk
931 Status = NtfsWriteDisk(Vcb->StorageDevice,
932 StartingOffset,
933 WriteLength,
934 Vcb->NtfsInfo.BytesPerSector,
935 (PVOID)SourceBuffer);
936
937 // Did the write fail?
938 if (!NT_SUCCESS(Status))
939 {
940 Context->CacheRun = DataRun;
941 Context->CacheRunOffset = Offset;
942 Context->CacheRunStartLCN = DataRunStartLCN;
943 Context->CacheRunLength = DataRunLength;
944 Context->CacheRunLastLCN = LastLCN;
945 Context->CacheRunCurrentOffset = CurrentOffset;
946
947 return Status;
948 }
949
950 Length -= WriteLength;
951 SourceBuffer += WriteLength;
952 *RealLengthWritten += WriteLength;
953
954 // Did we write to the end of the data run?
955 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
956 {
957 // Advance to the next data run
958 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
959 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
960
961 if (DataRunOffset != (ULONGLONG)-1)
962 {
963 DataRunStartLCN = LastLCN + DataRunOffset;
964 LastLCN = DataRunStartLCN;
965 }
966 else
967 DataRunStartLCN = -1;
968 }
969
970 // Do we have more data to write?
971 while (Length > 0)
972 {
973 // Make sure we don't write past the end of the current data run
974 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
975
976 // Are we dealing with a sparse data run?
977 if (DataRunStartLCN == -1)
978 {
979 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
980 return STATUS_NOT_IMPLEMENTED;
981 }
982 else
983 {
984 // write the data to the disk
985 Status = NtfsWriteDisk(Vcb->StorageDevice,
986 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
987 WriteLength,
988 Vcb->NtfsInfo.BytesPerSector,
989 (PVOID)SourceBuffer);
990 if (!NT_SUCCESS(Status))
991 break;
992 }
993
994 Length -= WriteLength;
995 SourceBuffer += WriteLength;
996 *RealLengthWritten += WriteLength;
997
998 // We finished this request, but there's still data in this data run.
999 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1000 break;
1001
1002 // Go to next run in the list.
1003
1004 if (*DataRun == 0)
1005 {
1006 // that was the last run
1007 if (Length > 0)
1008 {
1009 // Failed sanity check.
1010 DPRINT1("Encountered EOF before expected!\n");
1011 return STATUS_END_OF_FILE;
1012 }
1013
1014 break;
1015 }
1016
1017 // Advance to the next data run
1018 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1019 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1020 if (DataRunOffset != -1)
1021 {
1022 // Normal data run.
1023 DataRunStartLCN = LastLCN + DataRunOffset;
1024 LastLCN = DataRunStartLCN;
1025 }
1026 else
1027 {
1028 // Sparse data run.
1029 DataRunStartLCN = -1;
1030 }
1031 } // end while (Length > 0) [more data to write]
1032
1033 // TEMPTEMP
1034 if(Context->Record.IsNonResident)
1035 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1036
1037 Context->CacheRun = DataRun;
1038 Context->CacheRunOffset = Offset + *RealLengthWritten;
1039 Context->CacheRunStartLCN = DataRunStartLCN;
1040 Context->CacheRunLength = DataRunLength;
1041 Context->CacheRunLastLCN = LastLCN;
1042 Context->CacheRunCurrentOffset = CurrentOffset;
1043
1044 return Status;
1045 }
1046
1047 NTSTATUS
1048 ReadFileRecord(PDEVICE_EXTENSION Vcb,
1049 ULONGLONG index,
1050 PFILE_RECORD_HEADER file)
1051 {
1052 ULONGLONG BytesRead;
1053
1054 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
1055
1056 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
1057 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
1058 {
1059 DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
1060 return STATUS_PARTIAL_COPY;
1061 }
1062
1063 /* Apply update sequence array fixups. */
1064 DPRINT("Sequence number: %u\n", file->SequenceNumber);
1065 return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
1066 }
1067
1068
1069 /**
1070 * Searches a file's parent directory (given the parent's index in the mft)
1071 * for the given file. Upon finding an index entry for that file, updates
1072 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1073 *
1074 * (Most of this code was copied from NtfsFindMftRecord)
1075 */
1076 NTSTATUS
1077 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
1078 ULONGLONG ParentMFTIndex,
1079 PUNICODE_STRING FileName,
1080 BOOLEAN DirSearch,
1081 ULONGLONG NewDataSize,
1082 ULONGLONG NewAllocationSize)
1083 {
1084 PFILE_RECORD_HEADER MftRecord;
1085 PNTFS_ATTR_CONTEXT IndexRootCtx;
1086 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1087 PCHAR IndexRecord;
1088 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1089 NTSTATUS Status;
1090 ULONG CurrentEntry = 0;
1091
1092 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u)\n", Vcb, ParentMFTIndex, FileName, DirSearch, NewDataSize, NewAllocationSize);
1093
1094 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1095 Vcb->NtfsInfo.BytesPerFileRecord,
1096 TAG_NTFS);
1097 if (MftRecord == NULL)
1098 {
1099 return STATUS_INSUFFICIENT_RESOURCES;
1100 }
1101
1102 Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
1103 if (!NT_SUCCESS(Status))
1104 {
1105 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1106 return Status;
1107 }
1108
1109 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1110 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1111 if (!NT_SUCCESS(Status))
1112 {
1113 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1114 return Status;
1115 }
1116
1117 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1118 if (IndexRecord == NULL)
1119 {
1120 ReleaseAttributeContext(IndexRootCtx);
1121 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1122 return STATUS_INSUFFICIENT_RESOURCES;
1123 }
1124
1125 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
1126 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1127 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1128 // Index root is always resident.
1129 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1130
1131 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1132
1133 Status = UpdateIndexEntryFileNameSize(Vcb,
1134 MftRecord,
1135 IndexRecord,
1136 IndexRoot->SizeOfEntry,
1137 IndexEntry,
1138 IndexEntryEnd,
1139 FileName,
1140 &CurrentEntry,
1141 &CurrentEntry,
1142 DirSearch,
1143 NewDataSize,
1144 NewAllocationSize);
1145
1146 ReleaseAttributeContext(IndexRootCtx);
1147 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1148 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1149
1150 return Status;
1151 }
1152
1153 /**
1154 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1155 * proper index entry.
1156 * (Heavily based on BrowseIndexEntries)
1157 */
1158 NTSTATUS
1159 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
1160 PFILE_RECORD_HEADER MftRecord,
1161 PCHAR IndexRecord,
1162 ULONG IndexBlockSize,
1163 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1164 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1165 PUNICODE_STRING FileName,
1166 PULONG StartEntry,
1167 PULONG CurrentEntry,
1168 BOOLEAN DirSearch,
1169 ULONGLONG NewDataSize,
1170 ULONGLONG NewAllocatedSize)
1171 {
1172 NTSTATUS Status;
1173 ULONG RecordOffset;
1174 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1175 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1176 ULONGLONG IndexAllocationSize;
1177 PINDEX_BUFFER IndexBuffer;
1178
1179 DPRINT("UpdateIndexEntrySize(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %u, %I64u, %I64u)\n", Vcb, MftRecord, IndexRecord, IndexBlockSize, FirstEntry, LastEntry, FileName, *StartEntry, *CurrentEntry, DirSearch, NewDataSize, NewAllocatedSize);
1180
1181 // find the index entry responsible for the file we're trying to update
1182 IndexEntry = FirstEntry;
1183 while (IndexEntry < LastEntry &&
1184 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1185 {
1186 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
1187 *CurrentEntry >= *StartEntry &&
1188 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1189 CompareFileName(FileName, IndexEntry, DirSearch))
1190 {
1191 *StartEntry = *CurrentEntry;
1192 IndexEntry->FileName.DataSize = NewDataSize;
1193 IndexEntry->FileName.AllocatedSize = NewAllocatedSize;
1194 // indicate that the caller will still need to write the structure to the disk
1195 return STATUS_PENDING;
1196 }
1197
1198 (*CurrentEntry) += 1;
1199 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1200 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1201 }
1202
1203 /* If we're already browsing a subnode */
1204 if (IndexRecord == NULL)
1205 {
1206 return STATUS_OBJECT_PATH_NOT_FOUND;
1207 }
1208
1209 /* If there's no subnode */
1210 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1211 {
1212 return STATUS_OBJECT_PATH_NOT_FOUND;
1213 }
1214
1215 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1216 if (!NT_SUCCESS(Status))
1217 {
1218 DPRINT("Corrupted filesystem!\n");
1219 return Status;
1220 }
1221
1222 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1223 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1224 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1225 {
1226 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1227 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1228 if (!NT_SUCCESS(Status))
1229 {
1230 break;
1231 }
1232
1233 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1234 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1235 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1236 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1237 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1238 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1239
1240 Status = UpdateIndexEntryFileNameSize(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, NewDataSize, NewAllocatedSize);
1241 if (Status == STATUS_PENDING)
1242 {
1243 // write the index record back to disk
1244 ULONG Written;
1245
1246 // first we need to update the fixup values for the index block
1247 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1248 if (!NT_SUCCESS(Status))
1249 {
1250 DPRINT1("Error: Failed to update fixup sequence array!\n");
1251 break;
1252 }
1253
1254 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written);
1255 if (!NT_SUCCESS(Status))
1256 {
1257 DPRINT1("ERROR Performing write!\n");
1258 break;
1259 }
1260
1261 Status = STATUS_SUCCESS;
1262 break;
1263 }
1264 if (NT_SUCCESS(Status))
1265 {
1266 break;
1267 }
1268 }
1269
1270 ReleaseAttributeContext(IndexAllocationCtx);
1271 return Status;
1272 }
1273
1274 /**
1275 * @name UpdateFileRecord
1276 * @implemented
1277 *
1278 * Writes a file record to the master file table, at a given index.
1279 *
1280 * @param Vcb
1281 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1282 *
1283 * @param MftIndex
1284 * Target index in the master file table to store the file record.
1285 *
1286 * @param FileRecord
1287 * Pointer to the complete file record which will be written to the master file table.
1288 *
1289 * @return
1290 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1291 *
1292 */
1293 NTSTATUS
1294 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
1295 ULONGLONG MftIndex,
1296 PFILE_RECORD_HEADER FileRecord)
1297 {
1298 ULONG BytesWritten;
1299 NTSTATUS Status = STATUS_SUCCESS;
1300
1301 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord);
1302
1303 // Add the fixup array to prepare the data for writing to disk
1304 AddFixupArray(Vcb, &FileRecord->Ntfs);
1305
1306 // write the file record to the master file table
1307 Status = WriteAttribute(Vcb, Vcb->MFTContext, MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)FileRecord, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
1308
1309 if (!NT_SUCCESS(Status))
1310 {
1311 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
1312 }
1313
1314 // remove the fixup array (so the file record pointer can still be used)
1315 FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs);
1316
1317 return Status;
1318 }
1319
1320
1321 NTSTATUS
1322 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
1323 PNTFS_RECORD_HEADER Record)
1324 {
1325 USHORT *USA;
1326 USHORT USANumber;
1327 USHORT USACount;
1328 USHORT *Block;
1329
1330 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
1331 USANumber = *(USA++);
1332 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
1333 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
1334
1335 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
1336
1337 while (USACount)
1338 {
1339 if (*Block != USANumber)
1340 {
1341 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
1342 return STATUS_UNSUCCESSFUL;
1343 }
1344 *Block = *(USA++);
1345 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
1346 USACount--;
1347 }
1348
1349 return STATUS_SUCCESS;
1350 }
1351
1352 /**
1353 * @name AddNewMftEntry
1354 * @implemented
1355 *
1356 * Adds a file record to the master file table of a given device.
1357 *
1358 * @param FileRecord
1359 * Pointer to a complete file record which will be saved to disk.
1360 *
1361 * @param DeviceExt
1362 * Pointer to the DEVICE_EXTENSION of the target drive.
1363 *
1364 * @return
1365 * STATUS_SUCCESS on success.
1366 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1367 * to read the attribute.
1368 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1369 * STATUS_NOT_IMPLEMENTED if we need to increase the size of the MFT.
1370 *
1371 */
1372 NTSTATUS
1373 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
1374 PDEVICE_EXTENSION DeviceExt)
1375 {
1376 NTSTATUS Status = STATUS_SUCCESS;
1377 ULONGLONG MftIndex;
1378 RTL_BITMAP Bitmap;
1379 ULONGLONG BitmapDataSize;
1380 ULONGLONG AttrBytesRead;
1381 PVOID BitmapData;
1382 ULONG LengthWritten;
1383
1384 // First, we have to read the mft's $Bitmap attribute
1385 PNTFS_ATTR_CONTEXT BitmapContext;
1386 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
1387 if (!NT_SUCCESS(Status))
1388 {
1389 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1390 return Status;
1391 }
1392
1393 // allocate a buffer for the $Bitmap attribute
1394 BitmapDataSize = AttributeDataLength(&BitmapContext->Record);
1395 BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS);
1396 if (!BitmapData)
1397 {
1398 ReleaseAttributeContext(BitmapContext);
1399 return STATUS_INSUFFICIENT_RESOURCES;
1400 }
1401
1402 // read $Bitmap attribute
1403 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize);
1404
1405 if (AttrBytesRead == 0)
1406 {
1407 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1408 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1409 ReleaseAttributeContext(BitmapContext);
1410 return STATUS_OBJECT_NAME_NOT_FOUND;
1411 }
1412
1413 // convert buffer into bitmap
1414 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapDataSize * 8);
1415
1416 // set next available bit, preferrably after 23rd bit
1417 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
1418 if ((LONG)MftIndex == -1)
1419 {
1420 DPRINT1("ERROR: Couldn't find free space in MFT for file record!\n");
1421
1422 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1423 ReleaseAttributeContext(BitmapContext);
1424
1425 // TODO: increase mft size
1426 return STATUS_NOT_IMPLEMENTED;
1427 }
1428
1429 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
1430
1431 // update file record with index
1432 FileRecord->MFTRecordNumber = MftIndex;
1433
1434 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1435
1436 // write the bitmap back to the MFT's $Bitmap attribute
1437 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten);
1438 if (!NT_SUCCESS(Status))
1439 {
1440 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1441 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1442 ReleaseAttributeContext(BitmapContext);
1443 return Status;
1444 }
1445
1446 // update the file record (write it to disk)
1447 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
1448
1449 if (!NT_SUCCESS(Status))
1450 {
1451 DPRINT1("ERROR: Unable to write file record!\n");
1452 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1453 ReleaseAttributeContext(BitmapContext);
1454 return Status;
1455 }
1456
1457 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1458 ReleaseAttributeContext(BitmapContext);
1459
1460 return Status;
1461 }
1462
1463 NTSTATUS
1464 AddFixupArray(PDEVICE_EXTENSION Vcb,
1465 PNTFS_RECORD_HEADER Record)
1466 {
1467 USHORT *pShortToFixUp;
1468 unsigned int ArrayEntryCount = Record->UsaCount - 1;
1469 unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2;
1470 int i;
1471
1472 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
1473
1474 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
1475
1476 fixupArray->USN++;
1477
1478 for (i = 0; i < ArrayEntryCount; i++)
1479 {
1480 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
1481
1482 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
1483 fixupArray->Array[i] = *pShortToFixUp;
1484 *pShortToFixUp = fixupArray->USN;
1485 Offset += Vcb->NtfsInfo.BytesPerSector;
1486 }
1487
1488 return STATUS_SUCCESS;
1489 }
1490
1491 NTSTATUS
1492 ReadLCN(PDEVICE_EXTENSION Vcb,
1493 ULONGLONG lcn,
1494 ULONG count,
1495 PVOID buffer)
1496 {
1497 LARGE_INTEGER DiskSector;
1498
1499 DiskSector.QuadPart = lcn;
1500
1501 return NtfsReadSectors(Vcb->StorageDevice,
1502 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
1503 count * Vcb->NtfsInfo.SectorsPerCluster,
1504 Vcb->NtfsInfo.BytesPerSector,
1505 buffer,
1506 FALSE);
1507 }
1508
1509
1510 BOOLEAN
1511 CompareFileName(PUNICODE_STRING FileName,
1512 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
1513 BOOLEAN DirSearch)
1514 {
1515 BOOLEAN Ret, Alloc = FALSE;
1516 UNICODE_STRING EntryName;
1517
1518 EntryName.Buffer = IndexEntry->FileName.Name;
1519 EntryName.Length =
1520 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
1521
1522 if (DirSearch)
1523 {
1524 UNICODE_STRING IntFileName;
1525 if (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)
1526 {
1527 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
1528 Alloc = TRUE;
1529 }
1530 else
1531 {
1532 IntFileName = *FileName;
1533 }
1534
1535 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX), NULL);
1536
1537 if (Alloc)
1538 {
1539 RtlFreeUnicodeString(&IntFileName);
1540 }
1541
1542 return Ret;
1543 }
1544 else
1545 {
1546 return (RtlCompareUnicodeString(FileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)) == 0);
1547 }
1548 }
1549
1550 #if 0
1551 static
1552 VOID
1553 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
1554 {
1555 DPRINT1("Entry: %p\n", IndexEntry);
1556 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
1557 DPRINT1("\tLength: %u\n", IndexEntry->Length);
1558 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
1559 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
1560 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
1561 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
1562 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
1563 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
1564 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
1565 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
1566 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
1567 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
1568 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
1569 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
1570 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
1571 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
1572 }
1573 #endif
1574
1575 NTSTATUS
1576 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
1577 PFILE_RECORD_HEADER MftRecord,
1578 PCHAR IndexRecord,
1579 ULONG IndexBlockSize,
1580 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1581 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1582 PUNICODE_STRING FileName,
1583 PULONG StartEntry,
1584 PULONG CurrentEntry,
1585 BOOLEAN DirSearch,
1586 ULONGLONG *OutMFTIndex)
1587 {
1588 NTSTATUS Status;
1589 ULONG RecordOffset;
1590 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1591 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1592 ULONGLONG IndexAllocationSize;
1593 PINDEX_BUFFER IndexBuffer;
1594
1595 DPRINT("BrowseIndexEntries(%p, %p, %p, %u, %p, %p, %wZ, %u, %u, %u, %p)\n", Vcb, MftRecord, IndexRecord, IndexBlockSize, FirstEntry, LastEntry, FileName, *StartEntry, *CurrentEntry, DirSearch, OutMFTIndex);
1596
1597 IndexEntry = FirstEntry;
1598 while (IndexEntry < LastEntry &&
1599 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1600 {
1601 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
1602 *CurrentEntry >= *StartEntry &&
1603 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1604 CompareFileName(FileName, IndexEntry, DirSearch))
1605 {
1606 *StartEntry = *CurrentEntry;
1607 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
1608 return STATUS_SUCCESS;
1609 }
1610
1611 (*CurrentEntry) += 1;
1612 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1613 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1614 }
1615
1616 /* If we're already browsing a subnode */
1617 if (IndexRecord == NULL)
1618 {
1619 return STATUS_OBJECT_PATH_NOT_FOUND;
1620 }
1621
1622 /* If there's no subnode */
1623 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1624 {
1625 return STATUS_OBJECT_PATH_NOT_FOUND;
1626 }
1627
1628 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1629 if (!NT_SUCCESS(Status))
1630 {
1631 DPRINT("Corrupted filesystem!\n");
1632 return Status;
1633 }
1634
1635 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1636 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1637 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1638 {
1639 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1640 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1641 if (!NT_SUCCESS(Status))
1642 {
1643 break;
1644 }
1645
1646 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1647 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1648 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1649 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1650 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1651 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1652
1653 Status = BrowseIndexEntries(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, OutMFTIndex);
1654 if (NT_SUCCESS(Status))
1655 {
1656 break;
1657 }
1658 }
1659
1660 ReleaseAttributeContext(IndexAllocationCtx);
1661 return Status;
1662 }
1663
1664 NTSTATUS
1665 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
1666 ULONGLONG MFTIndex,
1667 PUNICODE_STRING FileName,
1668 PULONG FirstEntry,
1669 BOOLEAN DirSearch,
1670 ULONGLONG *OutMFTIndex)
1671 {
1672 PFILE_RECORD_HEADER MftRecord;
1673 PNTFS_ATTR_CONTEXT IndexRootCtx;
1674 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1675 PCHAR IndexRecord;
1676 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1677 NTSTATUS Status;
1678 ULONG CurrentEntry = 0;
1679
1680 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb, MFTIndex, FileName, *FirstEntry, DirSearch, OutMFTIndex);
1681
1682 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1683 Vcb->NtfsInfo.BytesPerFileRecord,
1684 TAG_NTFS);
1685 if (MftRecord == NULL)
1686 {
1687 return STATUS_INSUFFICIENT_RESOURCES;
1688 }
1689
1690 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
1691 if (!NT_SUCCESS(Status))
1692 {
1693 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1694 return Status;
1695 }
1696
1697 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1698 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1699 if (!NT_SUCCESS(Status))
1700 {
1701 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1702 return Status;
1703 }
1704
1705 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1706 if (IndexRecord == NULL)
1707 {
1708 ReleaseAttributeContext(IndexRootCtx);
1709 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1710 return STATUS_INSUFFICIENT_RESOURCES;
1711 }
1712
1713 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
1714 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1715 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1716 /* Index root is always resident. */
1717 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1718 ReleaseAttributeContext(IndexRootCtx);
1719
1720 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1721
1722 Status = BrowseIndexEntries(Vcb, MftRecord, IndexRecord, IndexRoot->SizeOfEntry, IndexEntry, IndexEntryEnd, FileName, FirstEntry, &CurrentEntry, DirSearch, OutMFTIndex);
1723
1724 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1725 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1726
1727 return Status;
1728 }
1729
1730 NTSTATUS
1731 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
1732 PUNICODE_STRING PathName,
1733 PFILE_RECORD_HEADER *FileRecord,
1734 PULONGLONG MFTIndex,
1735 ULONGLONG CurrentMFTIndex)
1736 {
1737 UNICODE_STRING Current, Remaining;
1738 NTSTATUS Status;
1739 ULONG FirstEntry = 0;
1740
1741 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb, PathName, FileRecord, CurrentMFTIndex);
1742
1743 FsRtlDissectName(*PathName, &Current, &Remaining);
1744
1745 while (Current.Length != 0)
1746 {
1747 DPRINT("Current: %wZ\n", &Current);
1748
1749 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex);
1750 if (!NT_SUCCESS(Status))
1751 {
1752 return Status;
1753 }
1754
1755 if (Remaining.Length == 0)
1756 break;
1757
1758 FsRtlDissectName(Current, &Current, &Remaining);
1759 }
1760
1761 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1762 if (*FileRecord == NULL)
1763 {
1764 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
1765 return STATUS_INSUFFICIENT_RESOURCES;
1766 }
1767
1768 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1769 if (!NT_SUCCESS(Status))
1770 {
1771 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
1772 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1773 return Status;
1774 }
1775
1776 *MFTIndex = CurrentMFTIndex;
1777
1778 return STATUS_SUCCESS;
1779 }
1780
1781 NTSTATUS
1782 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
1783 PUNICODE_STRING PathName,
1784 PFILE_RECORD_HEADER *FileRecord,
1785 PULONGLONG MFTIndex)
1786 {
1787 return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT);
1788 }
1789
1790 /**
1791 * @name NtfsDumpFileRecord
1792 * @implemented
1793 *
1794 * Provides diagnostic information about a file record. Prints a hex dump
1795 * of the entire record (based on the size reported by FileRecord->ByesInUse),
1796 * then prints a dump of each attribute.
1797 *
1798 * @param Vcb
1799 * Pointer to a DEVICE_EXTENSION describing the volume.
1800 *
1801 * @param FileRecord
1802 * Pointer to the file record to be analyzed.
1803 *
1804 * @remarks
1805 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
1806 * in size, and not just the header.
1807 *
1808 */
1809 VOID
1810 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
1811 PFILE_RECORD_HEADER FileRecord)
1812 {
1813 ULONG i, j;
1814
1815 // dump binary data, 8 bytes at a time
1816 for (i = 0; i < FileRecord->BytesInUse; i += 8)
1817 {
1818 // display current offset, in hex
1819 DbgPrint("\t%03x\t", i);
1820
1821 // display hex value of each of the next 8 bytes
1822 for (j = 0; j < 8; j++)
1823 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
1824 DbgPrint("\n");
1825 }
1826
1827 NtfsDumpFileAttributes(Vcb, FileRecord);
1828 }
1829
1830 NTSTATUS
1831 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
1832 PUNICODE_STRING SearchPattern,
1833 PULONG FirstEntry,
1834 PFILE_RECORD_HEADER *FileRecord,
1835 PULONGLONG MFTIndex,
1836 ULONGLONG CurrentMFTIndex)
1837 {
1838 NTSTATUS Status;
1839
1840 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb, SearchPattern, *FirstEntry, FileRecord, MFTIndex, CurrentMFTIndex);
1841
1842 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, &CurrentMFTIndex);
1843 if (!NT_SUCCESS(Status))
1844 {
1845 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
1846 return Status;
1847 }
1848
1849 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1850 if (*FileRecord == NULL)
1851 {
1852 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
1853 return STATUS_INSUFFICIENT_RESOURCES;
1854 }
1855
1856 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1857 if (!NT_SUCCESS(Status))
1858 {
1859 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
1860 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1861 return Status;
1862 }
1863
1864 *MFTIndex = CurrentMFTIndex;
1865
1866 return STATUS_SUCCESS;
1867 }
1868
1869 /* EOF */