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