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