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