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