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