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