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