ae2a6cc2baff8b50ecf00ecbfe64062d07d2e220
[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
54 Context->CacheRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
55 Context->CacheRunOffset = 0;
56 Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
57 Context->CacheRunLength = DataRunLength;
58 if (DataRunOffset != -1)
59 {
60 /* Normal run. */
61 Context->CacheRunStartLCN =
62 Context->CacheRunLastLCN = DataRunOffset;
63 }
64 else
65 {
66 /* Sparse run. */
67 Context->CacheRunStartLCN = -1;
68 Context->CacheRunLastLCN = 0;
69 }
70 Context->CacheRunCurrentOffset = 0;
71 }
72
73 return Context;
74 }
75
76
77 VOID
78 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
79 {
80 ExFreePoolWithTag(Context, TAG_NTFS);
81 }
82
83
84 /**
85 * @name FindAttribute
86 * @implemented
87 *
88 * Searches a file record for an attribute matching the given type and name.
89 *
90 * @param Offset
91 * Optional pointer to a ULONG that will receive the offset of the found attribute
92 * from the beginning of the record. Can be set to NULL.
93 */
94 NTSTATUS
95 FindAttribute(PDEVICE_EXTENSION Vcb,
96 PFILE_RECORD_HEADER MftRecord,
97 ULONG Type,
98 PCWSTR Name,
99 ULONG NameLength,
100 PNTFS_ATTR_CONTEXT * AttrCtx,
101 PULONG Offset)
102 {
103 BOOLEAN Found;
104 NTSTATUS Status;
105 FIND_ATTR_CONTXT Context;
106 PNTFS_ATTR_RECORD Attribute;
107
108 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %u, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx);
109
110 Found = FALSE;
111 Status = FindFirstAttribute(&Context, Vcb, MftRecord, FALSE, &Attribute);
112 while (NT_SUCCESS(Status))
113 {
114 if (Attribute->Type == Type && Attribute->NameLength == NameLength)
115 {
116 if (NameLength != 0)
117 {
118 PWCHAR AttrName;
119
120 AttrName = (PWCHAR)((PCHAR)Attribute + Attribute->NameOffset);
121 DPRINT("%.*S, %.*S\n", Attribute->NameLength, AttrName, NameLength, Name);
122 if (RtlCompareMemory(AttrName, Name, NameLength << 1) == (NameLength << 1))
123 {
124 Found = TRUE;
125 }
126 }
127 else
128 {
129 Found = TRUE;
130 }
131
132 if (Found)
133 {
134 /* Found it, fill up the context and return. */
135 DPRINT("Found context\n");
136 *AttrCtx = PrepareAttributeContext(Attribute);
137
138 if (Offset != NULL)
139 *Offset = Context.Offset;
140
141 FindCloseAttribute(&Context);
142 return STATUS_SUCCESS;
143 }
144 }
145
146 Status = FindNextAttribute(&Context, &Attribute);
147 }
148
149 FindCloseAttribute(&Context);
150 return STATUS_OBJECT_NAME_NOT_FOUND;
151 }
152
153
154 ULONG
155 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord)
156 {
157 if (AttrRecord->IsNonResident)
158 return AttrRecord->NonResident.AllocatedSize;
159 else
160 return AttrRecord->Resident.ValueLength;
161 }
162
163
164 ULONGLONG
165 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
166 {
167 if (AttrRecord->IsNonResident)
168 return AttrRecord->NonResident.DataSize;
169 else
170 return AttrRecord->Resident.ValueLength;
171 }
172
173
174 NTSTATUS
175 SetAttributeDataLength(PFILE_OBJECT FileObject,
176 PNTFS_FCB Fcb,
177 PNTFS_ATTR_CONTEXT AttrContext,
178 ULONG AttrOffset,
179 PFILE_RECORD_HEADER FileRecord,
180 PLARGE_INTEGER DataSize)
181 {
182 if (AttrContext->Record.IsNonResident)
183 {
184 // do we need to increase the allocation size?
185 if (AttrContext->Record.NonResident.AllocatedSize < DataSize->QuadPart)
186 {
187 DPRINT1("FixMe: Increasing allocation size is unimplemented!\n");
188 return STATUS_NOT_IMPLEMENTED;
189 }
190
191 // TODO: is the file compressed, encrypted, or sparse?
192
193 // NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource
194
195 // TODO: update the allocated size on-disk
196 DPRINT("Allocated Size: %I64u\n", AttrContext->Record.NonResident.AllocatedSize);
197
198 AttrContext->Record.NonResident.DataSize = DataSize->QuadPart;
199 AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
200
201 Fcb->RFCB.FileSize = *DataSize;
202 Fcb->RFCB.ValidDataLength = *DataSize;
203
204 DPRINT("Data Size: %I64u\n", Fcb->RFCB.FileSize.QuadPart);
205
206 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
207
208 // copy the attribute back into the FileRecord
209 RtlCopyMemory((PCHAR)FileRecord + AttrOffset, &AttrContext->Record, AttrContext->Record.Length);
210
211 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
212
213 // write the updated file record back to disk
214 UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord);
215
216 CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
217 }
218 else
219 {
220 // we can't yet handle resident attributes
221 DPRINT1("FixMe: Can't handle increasing length of resident attribute\n");
222 return STATUS_NOT_IMPLEMENTED;
223 }
224
225 return STATUS_SUCCESS;
226 }
227
228 ULONG
229 ReadAttribute(PDEVICE_EXTENSION Vcb,
230 PNTFS_ATTR_CONTEXT Context,
231 ULONGLONG Offset,
232 PCHAR Buffer,
233 ULONG Length)
234 {
235 ULONGLONG LastLCN;
236 PUCHAR DataRun;
237 LONGLONG DataRunOffset;
238 ULONGLONG DataRunLength;
239 LONGLONG DataRunStartLCN;
240 ULONGLONG CurrentOffset;
241 ULONG ReadLength;
242 ULONG AlreadyRead;
243 NTSTATUS Status;
244
245 if (!Context->Record.IsNonResident)
246 {
247 if (Offset > Context->Record.Resident.ValueLength)
248 return 0;
249 if (Offset + Length > Context->Record.Resident.ValueLength)
250 Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
251 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
252 return Length;
253 }
254
255 /*
256 * Non-resident attribute
257 */
258
259 /*
260 * I. Find the corresponding start data run.
261 */
262
263 AlreadyRead = 0;
264
265 // FIXME: Cache seems to be non-working. Disable it for now
266 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
267 if (0)
268 {
269 DataRun = Context->CacheRun;
270 LastLCN = Context->CacheRunLastLCN;
271 DataRunStartLCN = Context->CacheRunStartLCN;
272 DataRunLength = Context->CacheRunLength;
273 CurrentOffset = Context->CacheRunCurrentOffset;
274 }
275 else
276 {
277 LastLCN = 0;
278 DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
279 CurrentOffset = 0;
280
281 while (1)
282 {
283 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
284 if (DataRunOffset != -1)
285 {
286 /* Normal data run. */
287 DataRunStartLCN = LastLCN + DataRunOffset;
288 LastLCN = DataRunStartLCN;
289 }
290 else
291 {
292 /* Sparse data run. */
293 DataRunStartLCN = -1;
294 }
295
296 if (Offset >= CurrentOffset &&
297 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
298 {
299 break;
300 }
301
302 if (*DataRun == 0)
303 {
304 return AlreadyRead;
305 }
306
307 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
308 }
309 }
310
311 /*
312 * II. Go through the run list and read the data
313 */
314
315 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
316 if (DataRunStartLCN == -1)
317 {
318 RtlZeroMemory(Buffer, ReadLength);
319 Status = STATUS_SUCCESS;
320 }
321 else
322 {
323 Status = NtfsReadDisk(Vcb->StorageDevice,
324 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
325 ReadLength,
326 Vcb->NtfsInfo.BytesPerSector,
327 (PVOID)Buffer,
328 FALSE);
329 }
330 if (NT_SUCCESS(Status))
331 {
332 Length -= ReadLength;
333 Buffer += ReadLength;
334 AlreadyRead += ReadLength;
335
336 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
337 {
338 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
339 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
340 if (DataRunOffset != (ULONGLONG)-1)
341 {
342 DataRunStartLCN = LastLCN + DataRunOffset;
343 LastLCN = DataRunStartLCN;
344 }
345 else
346 DataRunStartLCN = -1;
347 }
348
349 while (Length > 0)
350 {
351 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
352 if (DataRunStartLCN == -1)
353 RtlZeroMemory(Buffer, ReadLength);
354 else
355 {
356 Status = NtfsReadDisk(Vcb->StorageDevice,
357 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
358 ReadLength,
359 Vcb->NtfsInfo.BytesPerSector,
360 (PVOID)Buffer,
361 FALSE);
362 if (!NT_SUCCESS(Status))
363 break;
364 }
365
366 Length -= ReadLength;
367 Buffer += ReadLength;
368 AlreadyRead += ReadLength;
369
370 /* We finished this request, but there still data in this data run. */
371 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
372 break;
373
374 /*
375 * Go to next run in the list.
376 */
377
378 if (*DataRun == 0)
379 break;
380 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
381 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
382 if (DataRunOffset != -1)
383 {
384 /* Normal data run. */
385 DataRunStartLCN = LastLCN + DataRunOffset;
386 LastLCN = DataRunStartLCN;
387 }
388 else
389 {
390 /* Sparse data run. */
391 DataRunStartLCN = -1;
392 }
393 } /* while */
394
395 } /* if Disk */
396
397 Context->CacheRun = DataRun;
398 Context->CacheRunOffset = Offset + AlreadyRead;
399 Context->CacheRunStartLCN = DataRunStartLCN;
400 Context->CacheRunLength = DataRunLength;
401 Context->CacheRunLastLCN = LastLCN;
402 Context->CacheRunCurrentOffset = CurrentOffset;
403
404 return AlreadyRead;
405 }
406
407
408 /**
409 * @name WriteAttribute
410 * @implemented
411 *
412 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
413 * and it still needs more documentation / cleaning up.
414 *
415 * @param Vcb
416 * Volume Control Block indicating which volume to write the attribute to
417 *
418 * @param Context
419 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
420 *
421 * @param Offset
422 * Offset, in bytes, from the beginning of the attribute indicating where to start
423 * writing data
424 *
425 * @param Buffer
426 * The data that's being written to the device
427 *
428 * @param Length
429 * How much data will be written, in bytes
430 *
431 * @param RealLengthWritten
432 * Pointer to a ULONG which will receive how much data was written, in bytes
433 *
434 * @return
435 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
436 * writing to a sparse file.
437 *
438 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
439 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
440 *
441 */
442
443 NTSTATUS
444 WriteAttribute(PDEVICE_EXTENSION Vcb,
445 PNTFS_ATTR_CONTEXT Context,
446 ULONGLONG Offset,
447 const PUCHAR Buffer,
448 ULONG Length,
449 PULONG RealLengthWritten)
450 {
451 ULONGLONG LastLCN;
452 PUCHAR DataRun;
453 LONGLONG DataRunOffset;
454 ULONGLONG DataRunLength;
455 LONGLONG DataRunStartLCN;
456 ULONGLONG CurrentOffset;
457 ULONG WriteLength;
458 NTSTATUS Status;
459 PUCHAR SourceBuffer = Buffer;
460 LONGLONG StartingOffset;
461
462 DPRINT("WriteAttribute(%p, %p, %I64U, %p, %lu)\n", Vcb, Context, Offset, Buffer, Length);
463
464 // is this a resident attribute?
465 if (!Context->Record.IsNonResident)
466 {
467 DPRINT1("FIXME: Writing to resident NTFS records (small files) is not supported at this time.\n");
468 // (TODO: This should be really easy to implement)
469
470 /* LeftOver code from ReadAttribute(), may be helpful:
471 if (Offset > Context->Record.Resident.ValueLength)
472 return 0;
473 if (Offset + Length > Context->Record.Resident.ValueLength)
474 Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
475 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
476 return Length;*/
477
478 return STATUS_NOT_IMPLEMENTED; // until we implement it
479 }
480
481 // This is a non-resident attribute.
482
483 // I. Find the corresponding start data run.
484
485 *RealLengthWritten = 0;
486
487 // FIXME: Cache seems to be non-working. Disable it for now
488 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
489 /*if (0)
490 {
491 DataRun = Context->CacheRun;
492 LastLCN = Context->CacheRunLastLCN;
493 DataRunStartLCN = Context->CacheRunStartLCN;
494 DataRunLength = Context->CacheRunLength;
495 CurrentOffset = Context->CacheRunCurrentOffset;
496 }
497 else*/
498 {
499 LastLCN = 0;
500 DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
501 CurrentOffset = 0;
502
503 while (1)
504 {
505 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
506 if (DataRunOffset != -1)
507 {
508 // Normal data run.
509 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
510 DataRunStartLCN = LastLCN + DataRunOffset;
511 LastLCN = DataRunStartLCN;
512 }
513 else
514 {
515 // Sparse data run. We can't support writing to sparse files yet
516 // (it may require increasing the allocation size).
517 DataRunStartLCN = -1;
518 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
519 return STATUS_NOT_IMPLEMENTED;
520 }
521
522 // Have we reached the data run we're trying to write to?
523 if (Offset >= CurrentOffset &&
524 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
525 {
526 break;
527 }
528
529 if (*DataRun == 0)
530 {
531 // We reached the last assigned cluster
532 // TODO: assign new clusters to the end of the file.
533 // (Presently, this code will never be reached, the write should have already failed by now)
534 return STATUS_END_OF_FILE;
535 }
536
537 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
538 }
539 }
540
541 // II. Go through the run list and write the data
542
543 /* REVIEWME -- As adapted from NtfsReadAttribute():
544 We seem to be making a special case for the first applicable data run, but I'm not sure why.
545 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
546
547 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
548
549 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
550
551 // Write the data to the disk
552 Status = NtfsWriteDisk(Vcb->StorageDevice,
553 StartingOffset,
554 WriteLength,
555 Vcb->NtfsInfo.BytesPerSector,
556 (PVOID)SourceBuffer);
557
558 // Did the write fail?
559 if (!NT_SUCCESS(Status))
560 {
561 Context->CacheRun = DataRun;
562 Context->CacheRunOffset = Offset;
563 Context->CacheRunStartLCN = DataRunStartLCN;
564 Context->CacheRunLength = DataRunLength;
565 Context->CacheRunLastLCN = LastLCN;
566 Context->CacheRunCurrentOffset = CurrentOffset;
567
568 return Status;
569 }
570
571 Length -= WriteLength;
572 SourceBuffer += WriteLength;
573 *RealLengthWritten += WriteLength;
574
575 // Did we write to the end of the data run?
576 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
577 {
578 // Advance to the next data run
579 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
580 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
581
582 if (DataRunOffset != (ULONGLONG)-1)
583 {
584 DataRunStartLCN = LastLCN + DataRunOffset;
585 LastLCN = DataRunStartLCN;
586 }
587 else
588 DataRunStartLCN = -1;
589 }
590
591 // Do we have more data to write?
592 while (Length > 0)
593 {
594 // Make sure we don't write past the end of the current data run
595 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
596
597 // Are we dealing with a sparse data run?
598 if (DataRunStartLCN == -1)
599 {
600 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
601 return STATUS_NOT_IMPLEMENTED;
602 }
603 else
604 {
605 // write the data to the disk
606 Status = NtfsWriteDisk(Vcb->StorageDevice,
607 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
608 WriteLength,
609 Vcb->NtfsInfo.BytesPerSector,
610 (PVOID)SourceBuffer);
611 if (!NT_SUCCESS(Status))
612 break;
613 }
614
615 Length -= WriteLength;
616 SourceBuffer += WriteLength;
617 *RealLengthWritten += WriteLength;
618
619 // We finished this request, but there's still data in this data run.
620 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
621 break;
622
623 // Go to next run in the list.
624
625 if (*DataRun == 0)
626 {
627 // that was the last run
628 if (Length > 0)
629 {
630 // Failed sanity check.
631 DPRINT1("Encountered EOF before expected!\n");
632 return STATUS_END_OF_FILE;
633 }
634
635 break;
636 }
637
638 // Advance to the next data run
639 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
640 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
641 if (DataRunOffset != -1)
642 {
643 // Normal data run.
644 DataRunStartLCN = LastLCN + DataRunOffset;
645 LastLCN = DataRunStartLCN;
646 }
647 else
648 {
649 // Sparse data run.
650 DataRunStartLCN = -1;
651 }
652 } // end while (Length > 0) [more data to write]
653
654 Context->CacheRun = DataRun;
655 Context->CacheRunOffset = Offset + *RealLengthWritten;
656 Context->CacheRunStartLCN = DataRunStartLCN;
657 Context->CacheRunLength = DataRunLength;
658 Context->CacheRunLastLCN = LastLCN;
659 Context->CacheRunCurrentOffset = CurrentOffset;
660
661 return Status;
662 }
663
664 NTSTATUS
665 ReadFileRecord(PDEVICE_EXTENSION Vcb,
666 ULONGLONG index,
667 PFILE_RECORD_HEADER file)
668 {
669 ULONGLONG BytesRead;
670
671 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
672
673 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
674 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
675 {
676 DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
677 return STATUS_PARTIAL_COPY;
678 }
679
680 /* Apply update sequence array fixups. */
681 return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
682 }
683
684
685 /**
686 * Searches a file's parent directory (given the parent's index in the mft)
687 * for the given file. Upon finding an index entry for that file, updates
688 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
689 *
690 * (Most of this code was copied from NtfsFindMftRecord)
691 */
692 NTSTATUS
693 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
694 ULONGLONG ParentMFTIndex,
695 PUNICODE_STRING FileName,
696 BOOLEAN DirSearch,
697 ULONGLONG NewDataSize,
698 ULONGLONG NewAllocationSize)
699 {
700 PFILE_RECORD_HEADER MftRecord;
701 PNTFS_ATTR_CONTEXT IndexRootCtx;
702 PINDEX_ROOT_ATTRIBUTE IndexRoot;
703 PCHAR IndexRecord;
704 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
705 NTSTATUS Status;
706 ULONG CurrentEntry = 0;
707
708 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u)\n", Vcb, ParentMFTIndex, FileName, DirSearch, NewDataSize, NewAllocationSize);
709
710 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
711 Vcb->NtfsInfo.BytesPerFileRecord,
712 TAG_NTFS);
713 if (MftRecord == NULL)
714 {
715 return STATUS_INSUFFICIENT_RESOURCES;
716 }
717
718 Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
719 if (!NT_SUCCESS(Status))
720 {
721 ExFreePoolWithTag(MftRecord, TAG_NTFS);
722 return Status;
723 }
724
725 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
726 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
727 if (!NT_SUCCESS(Status))
728 {
729 ExFreePoolWithTag(MftRecord, TAG_NTFS);
730 return Status;
731 }
732
733 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
734 if (IndexRecord == NULL)
735 {
736 ReleaseAttributeContext(IndexRootCtx);
737 ExFreePoolWithTag(MftRecord, TAG_NTFS);
738 return STATUS_INSUFFICIENT_RESOURCES;
739 }
740
741 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
742 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
743 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
744 // Index root is always resident.
745 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
746
747 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
748
749 Status = UpdateIndexEntryFileNameSize(Vcb,
750 MftRecord,
751 IndexRecord,
752 IndexRoot->SizeOfEntry,
753 IndexEntry,
754 IndexEntryEnd,
755 FileName,
756 &CurrentEntry,
757 &CurrentEntry,
758 DirSearch,
759 NewDataSize,
760 NewAllocationSize);
761
762 ReleaseAttributeContext(IndexRootCtx);
763 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
764 ExFreePoolWithTag(MftRecord, TAG_NTFS);
765
766 return Status;
767 }
768
769 /**
770 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
771 * proper index entry.
772 * (Heavily based on BrowseIndexEntries)
773 */
774 NTSTATUS
775 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
776 PFILE_RECORD_HEADER MftRecord,
777 PCHAR IndexRecord,
778 ULONG IndexBlockSize,
779 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
780 PINDEX_ENTRY_ATTRIBUTE LastEntry,
781 PUNICODE_STRING FileName,
782 PULONG StartEntry,
783 PULONG CurrentEntry,
784 BOOLEAN DirSearch,
785 ULONGLONG NewDataSize,
786 ULONGLONG NewAllocatedSize)
787 {
788 NTSTATUS Status;
789 ULONG RecordOffset;
790 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
791 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
792 ULONGLONG IndexAllocationSize;
793 PINDEX_BUFFER IndexBuffer;
794
795 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);
796
797 // find the index entry responsible for the file we're trying to update
798 IndexEntry = FirstEntry;
799 while (IndexEntry < LastEntry &&
800 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
801 {
802 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
803 *CurrentEntry >= *StartEntry &&
804 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
805 CompareFileName(FileName, IndexEntry, DirSearch))
806 {
807 *StartEntry = *CurrentEntry;
808 IndexEntry->FileName.DataSize = NewDataSize;
809 IndexEntry->FileName.AllocatedSize = NewAllocatedSize;
810 // indicate that the caller will still need to write the structure to the disk
811 return STATUS_PENDING;
812 }
813
814 (*CurrentEntry) += 1;
815 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
816 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
817 }
818
819 /* If we're already browsing a subnode */
820 if (IndexRecord == NULL)
821 {
822 return STATUS_OBJECT_PATH_NOT_FOUND;
823 }
824
825 /* If there's no subnode */
826 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
827 {
828 return STATUS_OBJECT_PATH_NOT_FOUND;
829 }
830
831 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
832 if (!NT_SUCCESS(Status))
833 {
834 DPRINT("Corrupted filesystem!\n");
835 return Status;
836 }
837
838 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
839 Status = STATUS_OBJECT_PATH_NOT_FOUND;
840 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
841 {
842 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
843 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
844 if (!NT_SUCCESS(Status))
845 {
846 break;
847 }
848
849 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
850 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
851 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
852 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
853 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
854 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
855
856 Status = UpdateIndexEntryFileNameSize(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, NewDataSize, NewAllocatedSize);
857 if (Status == STATUS_PENDING)
858 {
859 // write the index record back to disk
860 ULONG Written;
861
862 // first we need to update the fixup values for the index block
863 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
864 if (!NT_SUCCESS(Status))
865 {
866 DPRINT1("Error: Failed to update fixup sequence array!\n");
867 break;
868 }
869
870 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written);
871 if (!NT_SUCCESS(Status))
872 {
873 DPRINT1("ERROR Performing write!\n");
874 break;
875 }
876
877 Status = STATUS_SUCCESS;
878 break;
879 }
880 if (NT_SUCCESS(Status))
881 {
882 break;
883 }
884 }
885
886 ReleaseAttributeContext(IndexAllocationCtx);
887 return Status;
888 }
889
890 /**
891 * UpdateFileRecord
892 * @implemented
893 * Writes a file record to the master file table, at a given index.
894 */
895 NTSTATUS
896 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
897 ULONGLONG index,
898 PFILE_RECORD_HEADER file)
899 {
900 ULONG BytesWritten;
901 NTSTATUS Status = STATUS_SUCCESS;
902
903 DPRINT("UpdateFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
904
905 // Add the fixup array to prepare the data for writing to disk
906 AddFixupArray(Vcb, &file->Ntfs);
907
908 // write the file record to the master file table
909 Status = WriteAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
910
911 // TODO: Update MFT mirror
912
913 if (!NT_SUCCESS(Status))
914 {
915 DPRINT1("UpdateFileRecord failed: %I64u written, %u expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
916 }
917
918 return Status;
919 }
920
921
922 NTSTATUS
923 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
924 PNTFS_RECORD_HEADER Record)
925 {
926 USHORT *USA;
927 USHORT USANumber;
928 USHORT USACount;
929 USHORT *Block;
930
931 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
932 USANumber = *(USA++);
933 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
934 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
935
936 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
937
938 while (USACount)
939 {
940 if (*Block != USANumber)
941 {
942 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
943 return STATUS_UNSUCCESSFUL;
944 }
945 *Block = *(USA++);
946 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
947 USACount--;
948 }
949
950 return STATUS_SUCCESS;
951 }
952
953 NTSTATUS
954 AddFixupArray(PDEVICE_EXTENSION Vcb,
955 PNTFS_RECORD_HEADER Record)
956 {
957 USHORT *pShortToFixUp;
958 unsigned int ArrayEntryCount = Record->UsaCount - 1;
959 unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2;
960 int i;
961
962 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
963
964 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
965
966 fixupArray->USN++;
967
968 for (i = 0; i < ArrayEntryCount; i++)
969 {
970 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
971
972 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
973 fixupArray->Array[i] = *pShortToFixUp;
974 *pShortToFixUp = fixupArray->USN;
975 Offset += Vcb->NtfsInfo.BytesPerSector;
976 }
977
978 return STATUS_SUCCESS;
979 }
980
981 NTSTATUS
982 ReadLCN(PDEVICE_EXTENSION Vcb,
983 ULONGLONG lcn,
984 ULONG count,
985 PVOID buffer)
986 {
987 LARGE_INTEGER DiskSector;
988
989 DiskSector.QuadPart = lcn;
990
991 return NtfsReadSectors(Vcb->StorageDevice,
992 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
993 count * Vcb->NtfsInfo.SectorsPerCluster,
994 Vcb->NtfsInfo.BytesPerSector,
995 buffer,
996 FALSE);
997 }
998
999
1000 BOOLEAN
1001 CompareFileName(PUNICODE_STRING FileName,
1002 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
1003 BOOLEAN DirSearch)
1004 {
1005 BOOLEAN Ret, Alloc = FALSE;
1006 UNICODE_STRING EntryName;
1007
1008 EntryName.Buffer = IndexEntry->FileName.Name;
1009 EntryName.Length =
1010 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
1011
1012 if (DirSearch)
1013 {
1014 UNICODE_STRING IntFileName;
1015 if (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)
1016 {
1017 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
1018 Alloc = TRUE;
1019 }
1020 else
1021 {
1022 IntFileName = *FileName;
1023 }
1024
1025 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX), NULL);
1026
1027 if (Alloc)
1028 {
1029 RtlFreeUnicodeString(&IntFileName);
1030 }
1031
1032 return Ret;
1033 }
1034 else
1035 {
1036 return (RtlCompareUnicodeString(FileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)) == 0);
1037 }
1038 }
1039
1040 #if 0
1041 static
1042 VOID
1043 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
1044 {
1045 DPRINT1("Entry: %p\n", IndexEntry);
1046 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
1047 DPRINT1("\tLength: %u\n", IndexEntry->Length);
1048 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
1049 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
1050 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
1051 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
1052 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
1053 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
1054 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
1055 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
1056 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
1057 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
1058 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
1059 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
1060 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
1061 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
1062 }
1063 #endif
1064
1065 NTSTATUS
1066 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
1067 PFILE_RECORD_HEADER MftRecord,
1068 PCHAR IndexRecord,
1069 ULONG IndexBlockSize,
1070 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1071 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1072 PUNICODE_STRING FileName,
1073 PULONG StartEntry,
1074 PULONG CurrentEntry,
1075 BOOLEAN DirSearch,
1076 ULONGLONG *OutMFTIndex)
1077 {
1078 NTSTATUS Status;
1079 ULONG RecordOffset;
1080 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1081 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1082 ULONGLONG IndexAllocationSize;
1083 PINDEX_BUFFER IndexBuffer;
1084
1085 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);
1086
1087 IndexEntry = FirstEntry;
1088 while (IndexEntry < LastEntry &&
1089 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1090 {
1091 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
1092 *CurrentEntry >= *StartEntry &&
1093 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1094 CompareFileName(FileName, IndexEntry, DirSearch))
1095 {
1096 *StartEntry = *CurrentEntry;
1097 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
1098 return STATUS_SUCCESS;
1099 }
1100
1101 (*CurrentEntry) += 1;
1102 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1103 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1104 }
1105
1106 /* If we're already browsing a subnode */
1107 if (IndexRecord == NULL)
1108 {
1109 return STATUS_OBJECT_PATH_NOT_FOUND;
1110 }
1111
1112 /* If there's no subnode */
1113 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1114 {
1115 return STATUS_OBJECT_PATH_NOT_FOUND;
1116 }
1117
1118 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1119 if (!NT_SUCCESS(Status))
1120 {
1121 DPRINT("Corrupted filesystem!\n");
1122 return Status;
1123 }
1124
1125 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1126 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1127 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1128 {
1129 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1130 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1131 if (!NT_SUCCESS(Status))
1132 {
1133 break;
1134 }
1135
1136 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1137 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1138 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1139 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1140 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1141 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1142
1143 Status = BrowseIndexEntries(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, OutMFTIndex);
1144 if (NT_SUCCESS(Status))
1145 {
1146 break;
1147 }
1148 }
1149
1150 ReleaseAttributeContext(IndexAllocationCtx);
1151 return Status;
1152 }
1153
1154 NTSTATUS
1155 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
1156 ULONGLONG MFTIndex,
1157 PUNICODE_STRING FileName,
1158 PULONG FirstEntry,
1159 BOOLEAN DirSearch,
1160 ULONGLONG *OutMFTIndex)
1161 {
1162 PFILE_RECORD_HEADER MftRecord;
1163 PNTFS_ATTR_CONTEXT IndexRootCtx;
1164 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1165 PCHAR IndexRecord;
1166 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1167 NTSTATUS Status;
1168 ULONG CurrentEntry = 0;
1169
1170 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb, MFTIndex, FileName, *FirstEntry, DirSearch, OutMFTIndex);
1171
1172 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1173 Vcb->NtfsInfo.BytesPerFileRecord,
1174 TAG_NTFS);
1175 if (MftRecord == NULL)
1176 {
1177 return STATUS_INSUFFICIENT_RESOURCES;
1178 }
1179
1180 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
1181 if (!NT_SUCCESS(Status))
1182 {
1183 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1184 return Status;
1185 }
1186
1187 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1188 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1189 if (!NT_SUCCESS(Status))
1190 {
1191 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1192 return Status;
1193 }
1194
1195 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1196 if (IndexRecord == NULL)
1197 {
1198 ReleaseAttributeContext(IndexRootCtx);
1199 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1200 return STATUS_INSUFFICIENT_RESOURCES;
1201 }
1202
1203 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
1204 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1205 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1206 /* Index root is always resident. */
1207 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1208 ReleaseAttributeContext(IndexRootCtx);
1209
1210 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1211
1212 Status = BrowseIndexEntries(Vcb, MftRecord, IndexRecord, IndexRoot->SizeOfEntry, IndexEntry, IndexEntryEnd, FileName, FirstEntry, &CurrentEntry, DirSearch, OutMFTIndex);
1213
1214 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1215 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1216
1217 return Status;
1218 }
1219
1220 NTSTATUS
1221 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
1222 PUNICODE_STRING PathName,
1223 PFILE_RECORD_HEADER *FileRecord,
1224 PULONGLONG MFTIndex,
1225 ULONGLONG CurrentMFTIndex)
1226 {
1227 UNICODE_STRING Current, Remaining;
1228 NTSTATUS Status;
1229 ULONG FirstEntry = 0;
1230
1231 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb, PathName, FileRecord, CurrentMFTIndex);
1232
1233 FsRtlDissectName(*PathName, &Current, &Remaining);
1234
1235 while (Current.Length != 0)
1236 {
1237 DPRINT("Current: %wZ\n", &Current);
1238
1239 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex);
1240 if (!NT_SUCCESS(Status))
1241 {
1242 return Status;
1243 }
1244
1245 if (Remaining.Length == 0)
1246 break;
1247
1248 FsRtlDissectName(Current, &Current, &Remaining);
1249 }
1250
1251 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1252 if (*FileRecord == NULL)
1253 {
1254 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
1255 return STATUS_INSUFFICIENT_RESOURCES;
1256 }
1257
1258 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1259 if (!NT_SUCCESS(Status))
1260 {
1261 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
1262 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1263 return Status;
1264 }
1265
1266 *MFTIndex = CurrentMFTIndex;
1267
1268 return STATUS_SUCCESS;
1269 }
1270
1271 NTSTATUS
1272 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
1273 PUNICODE_STRING PathName,
1274 PFILE_RECORD_HEADER *FileRecord,
1275 PULONGLONG MFTIndex)
1276 {
1277 return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT);
1278 }
1279
1280 NTSTATUS
1281 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
1282 PUNICODE_STRING SearchPattern,
1283 PULONG FirstEntry,
1284 PFILE_RECORD_HEADER *FileRecord,
1285 PULONGLONG MFTIndex,
1286 ULONGLONG CurrentMFTIndex)
1287 {
1288 NTSTATUS Status;
1289
1290 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb, SearchPattern, *FirstEntry, FileRecord, MFTIndex, CurrentMFTIndex);
1291
1292 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, &CurrentMFTIndex);
1293 if (!NT_SUCCESS(Status))
1294 {
1295 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
1296 return Status;
1297 }
1298
1299 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1300 if (*FileRecord == NULL)
1301 {
1302 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
1303 return STATUS_INSUFFICIENT_RESOURCES;
1304 }
1305
1306 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1307 if (!NT_SUCCESS(Status))
1308 {
1309 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
1310 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1311 return Status;
1312 }
1313
1314 *MFTIndex = CurrentMFTIndex;
1315
1316 return STATUS_SUCCESS;
1317 }
1318
1319 /* EOF */