c7096cc5d8f5409662cb35166131125675beab11
[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 // remove the fixup array (so the file record pointer can still be used)
1088 FixupUpdateSequenceArray(Vcb, &file->Ntfs);
1089
1090 return Status;
1091 }
1092
1093
1094 NTSTATUS
1095 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
1096 PNTFS_RECORD_HEADER Record)
1097 {
1098 USHORT *USA;
1099 USHORT USANumber;
1100 USHORT USACount;
1101 USHORT *Block;
1102
1103 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
1104 USANumber = *(USA++);
1105 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
1106 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
1107
1108 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
1109
1110 while (USACount)
1111 {
1112 if (*Block != USANumber)
1113 {
1114 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
1115 return STATUS_UNSUCCESSFUL;
1116 }
1117 *Block = *(USA++);
1118 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
1119 USACount--;
1120 }
1121
1122 return STATUS_SUCCESS;
1123 }
1124
1125 NTSTATUS
1126 AddFixupArray(PDEVICE_EXTENSION Vcb,
1127 PNTFS_RECORD_HEADER Record)
1128 {
1129 USHORT *pShortToFixUp;
1130 unsigned int ArrayEntryCount = Record->UsaCount - 1;
1131 unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2;
1132 int i;
1133
1134 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
1135
1136 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
1137
1138 fixupArray->USN++;
1139
1140 for (i = 0; i < ArrayEntryCount; i++)
1141 {
1142 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
1143
1144 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
1145 fixupArray->Array[i] = *pShortToFixUp;
1146 *pShortToFixUp = fixupArray->USN;
1147 Offset += Vcb->NtfsInfo.BytesPerSector;
1148 }
1149
1150 return STATUS_SUCCESS;
1151 }
1152
1153 NTSTATUS
1154 ReadLCN(PDEVICE_EXTENSION Vcb,
1155 ULONGLONG lcn,
1156 ULONG count,
1157 PVOID buffer)
1158 {
1159 LARGE_INTEGER DiskSector;
1160
1161 DiskSector.QuadPart = lcn;
1162
1163 return NtfsReadSectors(Vcb->StorageDevice,
1164 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
1165 count * Vcb->NtfsInfo.SectorsPerCluster,
1166 Vcb->NtfsInfo.BytesPerSector,
1167 buffer,
1168 FALSE);
1169 }
1170
1171
1172 BOOLEAN
1173 CompareFileName(PUNICODE_STRING FileName,
1174 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
1175 BOOLEAN DirSearch)
1176 {
1177 BOOLEAN Ret, Alloc = FALSE;
1178 UNICODE_STRING EntryName;
1179
1180 EntryName.Buffer = IndexEntry->FileName.Name;
1181 EntryName.Length =
1182 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
1183
1184 if (DirSearch)
1185 {
1186 UNICODE_STRING IntFileName;
1187 if (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)
1188 {
1189 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
1190 Alloc = TRUE;
1191 }
1192 else
1193 {
1194 IntFileName = *FileName;
1195 }
1196
1197 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX), NULL);
1198
1199 if (Alloc)
1200 {
1201 RtlFreeUnicodeString(&IntFileName);
1202 }
1203
1204 return Ret;
1205 }
1206 else
1207 {
1208 return (RtlCompareUnicodeString(FileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)) == 0);
1209 }
1210 }
1211
1212 #if 0
1213 static
1214 VOID
1215 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
1216 {
1217 DPRINT1("Entry: %p\n", IndexEntry);
1218 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
1219 DPRINT1("\tLength: %u\n", IndexEntry->Length);
1220 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
1221 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
1222 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
1223 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
1224 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
1225 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
1226 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
1227 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
1228 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
1229 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
1230 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
1231 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
1232 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
1233 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
1234 }
1235 #endif
1236
1237 NTSTATUS
1238 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
1239 PFILE_RECORD_HEADER MftRecord,
1240 PCHAR IndexRecord,
1241 ULONG IndexBlockSize,
1242 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1243 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1244 PUNICODE_STRING FileName,
1245 PULONG StartEntry,
1246 PULONG CurrentEntry,
1247 BOOLEAN DirSearch,
1248 ULONGLONG *OutMFTIndex)
1249 {
1250 NTSTATUS Status;
1251 ULONG RecordOffset;
1252 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1253 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1254 ULONGLONG IndexAllocationSize;
1255 PINDEX_BUFFER IndexBuffer;
1256
1257 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);
1258
1259 IndexEntry = FirstEntry;
1260 while (IndexEntry < LastEntry &&
1261 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1262 {
1263 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
1264 *CurrentEntry >= *StartEntry &&
1265 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1266 CompareFileName(FileName, IndexEntry, DirSearch))
1267 {
1268 *StartEntry = *CurrentEntry;
1269 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
1270 return STATUS_SUCCESS;
1271 }
1272
1273 (*CurrentEntry) += 1;
1274 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1275 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1276 }
1277
1278 /* If we're already browsing a subnode */
1279 if (IndexRecord == NULL)
1280 {
1281 return STATUS_OBJECT_PATH_NOT_FOUND;
1282 }
1283
1284 /* If there's no subnode */
1285 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1286 {
1287 return STATUS_OBJECT_PATH_NOT_FOUND;
1288 }
1289
1290 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1291 if (!NT_SUCCESS(Status))
1292 {
1293 DPRINT("Corrupted filesystem!\n");
1294 return Status;
1295 }
1296
1297 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1298 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1299 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1300 {
1301 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1302 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1303 if (!NT_SUCCESS(Status))
1304 {
1305 break;
1306 }
1307
1308 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1309 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1310 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1311 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1312 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1313 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1314
1315 Status = BrowseIndexEntries(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, OutMFTIndex);
1316 if (NT_SUCCESS(Status))
1317 {
1318 break;
1319 }
1320 }
1321
1322 ReleaseAttributeContext(IndexAllocationCtx);
1323 return Status;
1324 }
1325
1326 NTSTATUS
1327 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
1328 ULONGLONG MFTIndex,
1329 PUNICODE_STRING FileName,
1330 PULONG FirstEntry,
1331 BOOLEAN DirSearch,
1332 ULONGLONG *OutMFTIndex)
1333 {
1334 PFILE_RECORD_HEADER MftRecord;
1335 PNTFS_ATTR_CONTEXT IndexRootCtx;
1336 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1337 PCHAR IndexRecord;
1338 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1339 NTSTATUS Status;
1340 ULONG CurrentEntry = 0;
1341
1342 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb, MFTIndex, FileName, *FirstEntry, DirSearch, OutMFTIndex);
1343
1344 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1345 Vcb->NtfsInfo.BytesPerFileRecord,
1346 TAG_NTFS);
1347 if (MftRecord == NULL)
1348 {
1349 return STATUS_INSUFFICIENT_RESOURCES;
1350 }
1351
1352 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
1353 if (!NT_SUCCESS(Status))
1354 {
1355 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1356 return Status;
1357 }
1358
1359 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1360 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1361 if (!NT_SUCCESS(Status))
1362 {
1363 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1364 return Status;
1365 }
1366
1367 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1368 if (IndexRecord == NULL)
1369 {
1370 ReleaseAttributeContext(IndexRootCtx);
1371 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1372 return STATUS_INSUFFICIENT_RESOURCES;
1373 }
1374
1375 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
1376 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1377 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1378 /* Index root is always resident. */
1379 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1380 ReleaseAttributeContext(IndexRootCtx);
1381
1382 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1383
1384 Status = BrowseIndexEntries(Vcb, MftRecord, IndexRecord, IndexRoot->SizeOfEntry, IndexEntry, IndexEntryEnd, FileName, FirstEntry, &CurrentEntry, DirSearch, OutMFTIndex);
1385
1386 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1387 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1388
1389 return Status;
1390 }
1391
1392 NTSTATUS
1393 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
1394 PUNICODE_STRING PathName,
1395 PFILE_RECORD_HEADER *FileRecord,
1396 PULONGLONG MFTIndex,
1397 ULONGLONG CurrentMFTIndex)
1398 {
1399 UNICODE_STRING Current, Remaining;
1400 NTSTATUS Status;
1401 ULONG FirstEntry = 0;
1402
1403 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb, PathName, FileRecord, CurrentMFTIndex);
1404
1405 FsRtlDissectName(*PathName, &Current, &Remaining);
1406
1407 while (Current.Length != 0)
1408 {
1409 DPRINT("Current: %wZ\n", &Current);
1410
1411 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex);
1412 if (!NT_SUCCESS(Status))
1413 {
1414 return Status;
1415 }
1416
1417 if (Remaining.Length == 0)
1418 break;
1419
1420 FsRtlDissectName(Current, &Current, &Remaining);
1421 }
1422
1423 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1424 if (*FileRecord == NULL)
1425 {
1426 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
1427 return STATUS_INSUFFICIENT_RESOURCES;
1428 }
1429
1430 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1431 if (!NT_SUCCESS(Status))
1432 {
1433 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
1434 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1435 return Status;
1436 }
1437
1438 *MFTIndex = CurrentMFTIndex;
1439
1440 return STATUS_SUCCESS;
1441 }
1442
1443 NTSTATUS
1444 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
1445 PUNICODE_STRING PathName,
1446 PFILE_RECORD_HEADER *FileRecord,
1447 PULONGLONG MFTIndex)
1448 {
1449 return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT);
1450 }
1451
1452 NTSTATUS
1453 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
1454 PUNICODE_STRING SearchPattern,
1455 PULONG FirstEntry,
1456 PFILE_RECORD_HEADER *FileRecord,
1457 PULONGLONG MFTIndex,
1458 ULONGLONG CurrentMFTIndex)
1459 {
1460 NTSTATUS Status;
1461
1462 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb, SearchPattern, *FirstEntry, FileRecord, MFTIndex, CurrentMFTIndex);
1463
1464 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, &CurrentMFTIndex);
1465 if (!NT_SUCCESS(Status))
1466 {
1467 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
1468 return Status;
1469 }
1470
1471 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1472 if (*FileRecord == NULL)
1473 {
1474 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
1475 return STATUS_INSUFFICIENT_RESOURCES;
1476 }
1477
1478 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1479 if (!NT_SUCCESS(Status))
1480 {
1481 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
1482 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1483 return Status;
1484 }
1485
1486 *MFTIndex = CurrentMFTIndex;
1487
1488 return STATUS_SUCCESS;
1489 }
1490
1491 /* EOF */