[NTFS] - Expand support for resizing resident attributes and fix NtfsAllocateClusters().
[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 ULONGLONG NextVBN = 0;
54 PUCHAR DataRun = (PUCHAR)&Context->Record + Context->Record.NonResident.MappingPairsOffset;
55
56 Context->CacheRun = DataRun;
57 Context->CacheRunOffset = 0;
58 Context->CacheRun = DecodeRun(Context->CacheRun, &DataRunOffset, &DataRunLength);
59 Context->CacheRunLength = DataRunLength;
60 if (DataRunOffset != -1)
61 {
62 /* Normal run. */
63 Context->CacheRunStartLCN =
64 Context->CacheRunLastLCN = DataRunOffset;
65 }
66 else
67 {
68 /* Sparse run. */
69 Context->CacheRunStartLCN = -1;
70 Context->CacheRunLastLCN = 0;
71 }
72 Context->CacheRunCurrentOffset = 0;
73
74 // Convert the data runs to a map control block
75 if (!NT_SUCCESS(ConvertDataRunsToLargeMCB(DataRun, &Context->DataRunsMCB, &NextVBN)))
76 {
77 DPRINT1("Unable to convert data runs to MCB!\n");
78 ExFreePoolWithTag(Context, TAG_NTFS);
79 return NULL;
80 }
81 }
82
83 return Context;
84 }
85
86
87 VOID
88 ReleaseAttributeContext(PNTFS_ATTR_CONTEXT Context)
89 {
90 if (Context->Record.IsNonResident)
91 {
92 FsRtlUninitializeLargeMcb(&Context->DataRunsMCB);
93 }
94
95 ExFreePoolWithTag(Context, TAG_NTFS);
96 }
97
98
99 /**
100 * @name FindAttribute
101 * @implemented
102 *
103 * Searches a file record for an attribute matching the given type and name.
104 *
105 * @param Offset
106 * Optional pointer to a ULONG that will receive the offset of the found attribute
107 * from the beginning of the record. Can be set to NULL.
108 */
109 NTSTATUS
110 FindAttribute(PDEVICE_EXTENSION Vcb,
111 PFILE_RECORD_HEADER MftRecord,
112 ULONG Type,
113 PCWSTR Name,
114 ULONG NameLength,
115 PNTFS_ATTR_CONTEXT * AttrCtx,
116 PULONG Offset)
117 {
118 BOOLEAN Found;
119 NTSTATUS Status;
120 FIND_ATTR_CONTXT Context;
121 PNTFS_ATTR_RECORD Attribute;
122
123 DPRINT("FindAttribute(%p, %p, 0x%x, %S, %u, %p)\n", Vcb, MftRecord, Type, Name, NameLength, AttrCtx);
124
125 Found = FALSE;
126 Status = FindFirstAttribute(&Context, Vcb, MftRecord, FALSE, &Attribute);
127 while (NT_SUCCESS(Status))
128 {
129 if (Attribute->Type == Type && Attribute->NameLength == NameLength)
130 {
131 if (NameLength != 0)
132 {
133 PWCHAR AttrName;
134
135 AttrName = (PWCHAR)((PCHAR)Attribute + Attribute->NameOffset);
136 DPRINT("%.*S, %.*S\n", Attribute->NameLength, AttrName, NameLength, Name);
137 if (RtlCompareMemory(AttrName, Name, NameLength << 1) == (NameLength << 1))
138 {
139 Found = TRUE;
140 }
141 }
142 else
143 {
144 Found = TRUE;
145 }
146
147 if (Found)
148 {
149 /* Found it, fill up the context and return. */
150 DPRINT("Found context\n");
151 *AttrCtx = PrepareAttributeContext(Attribute);
152
153 (*AttrCtx)->FileMFTIndex = MftRecord->MFTRecordNumber;
154
155 if (Offset != NULL)
156 *Offset = Context.Offset;
157
158 FindCloseAttribute(&Context);
159 return STATUS_SUCCESS;
160 }
161 }
162
163 Status = FindNextAttribute(&Context, &Attribute);
164 }
165
166 FindCloseAttribute(&Context);
167 return STATUS_OBJECT_NAME_NOT_FOUND;
168 }
169
170
171 ULONGLONG
172 AttributeAllocatedLength(PNTFS_ATTR_RECORD AttrRecord)
173 {
174 if (AttrRecord->IsNonResident)
175 return AttrRecord->NonResident.AllocatedSize;
176 else
177 return AttrRecord->Resident.ValueLength;
178 }
179
180
181 ULONGLONG
182 AttributeDataLength(PNTFS_ATTR_RECORD AttrRecord)
183 {
184 if (AttrRecord->IsNonResident)
185 return AttrRecord->NonResident.DataSize;
186 else
187 return AttrRecord->Resident.ValueLength;
188 }
189
190 VOID
191 InternalSetResidentAttributeLength(PNTFS_ATTR_CONTEXT AttrContext,
192 PFILE_RECORD_HEADER FileRecord,
193 ULONG AttrOffset,
194 ULONG DataSize)
195 {
196 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
197 ULONG NextAttributeOffset;
198
199 DPRINT("InternalSetResidentAttributeLength( %p, %p, %lu, %lu )\n", AttrContext, FileRecord, AttrOffset, DataSize);
200
201 // update ValueLength Field
202 AttrContext->Record.Resident.ValueLength =
203 Destination->Resident.ValueLength = DataSize;
204
205 // calculate the record length and end marker offset
206 AttrContext->Record.Length =
207 Destination->Length = DataSize + AttrContext->Record.Resident.ValueOffset;
208 NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
209
210 // Ensure NextAttributeOffset is aligned to an 8-byte boundary
211 if (NextAttributeOffset % 8 != 0)
212 {
213 USHORT Padding = 8 - (NextAttributeOffset % 8);
214 NextAttributeOffset += Padding;
215 AttrContext->Record.Length += Padding;
216 Destination->Length += Padding;
217 }
218
219 // advance Destination to the final "attribute" and set the file record end
220 Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)Destination + Destination->Length);
221 SetFileRecordEnd(FileRecord, Destination, FILE_RECORD_END);
222 }
223
224 /**
225 * @parameter FileRecord
226 * Pointer to a file record. Must be a full record at least
227 * Fcb->Vcb->NtfsInfo.BytesPerFileRecord bytes large, not just the header.
228 */
229 NTSTATUS
230 SetAttributeDataLength(PFILE_OBJECT FileObject,
231 PNTFS_FCB Fcb,
232 PNTFS_ATTR_CONTEXT AttrContext,
233 ULONG AttrOffset,
234 PFILE_RECORD_HEADER FileRecord,
235 PLARGE_INTEGER DataSize)
236 {
237 NTSTATUS Status = STATUS_SUCCESS;
238 ULONG BytesPerCluster = Fcb->Vcb->NtfsInfo.BytesPerCluster;
239
240 // are we truncating the file?
241 if (DataSize->QuadPart < AttributeDataLength(&AttrContext->Record))
242 {
243 if (!MmCanFileBeTruncated(FileObject->SectionObjectPointer, DataSize))
244 {
245 DPRINT1("Can't truncate a memory-mapped file!\n");
246 return STATUS_USER_MAPPED_FILE;
247 }
248 }
249
250 if (AttrContext->Record.IsNonResident)
251 {
252 ULONGLONG AllocationSize = ROUND_UP(DataSize->QuadPart, BytesPerCluster);
253 PNTFS_ATTR_RECORD DestinationAttribute = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
254 ULONG ExistingClusters = AttrContext->Record.NonResident.AllocatedSize / BytesPerCluster;
255
256 // do we need to increase the allocation size?
257 if (AttrContext->Record.NonResident.AllocatedSize < AllocationSize)
258 {
259 ULONG ClustersNeeded = (AllocationSize / BytesPerCluster) - ExistingClusters;
260 LARGE_INTEGER LastClusterInDataRun;
261 ULONG NextAssignedCluster;
262 ULONG AssignedClusters;
263
264 if (ExistingClusters == 0)
265 {
266 LastClusterInDataRun.QuadPart = 0;
267 }
268 else
269 {
270 if (!FsRtlLookupLargeMcbEntry(&AttrContext->DataRunsMCB,
271 (LONGLONG)AttrContext->Record.NonResident.HighestVCN,
272 (PLONGLONG)&LastClusterInDataRun.QuadPart,
273 NULL,
274 NULL,
275 NULL,
276 NULL))
277 {
278 DPRINT1("Error looking up final large MCB entry!\n");
279
280 // Most likely, HighestVCN went above the largest mapping
281 DPRINT1("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
282 return STATUS_INVALID_PARAMETER;
283 }
284 }
285
286 DPRINT("LastClusterInDataRun: %I64u\n", LastClusterInDataRun.QuadPart);
287 DPRINT("Highest VCN of record: %I64u\n", AttrContext->Record.NonResident.HighestVCN);
288
289 while (ClustersNeeded > 0)
290 {
291 Status = NtfsAllocateClusters(Fcb->Vcb,
292 LastClusterInDataRun.LowPart + 1,
293 ClustersNeeded,
294 &NextAssignedCluster,
295 &AssignedClusters);
296
297 if (!NT_SUCCESS(Status))
298 {
299 DPRINT1("Error: Unable to allocate requested clusters!\n");
300 return Status;
301 }
302
303 // now we need to add the clusters we allocated to the data run
304 Status = AddRun(Fcb->Vcb, AttrContext, AttrOffset, FileRecord, NextAssignedCluster, AssignedClusters);
305 if (!NT_SUCCESS(Status))
306 {
307 DPRINT1("Error: Unable to add data run!\n");
308 return Status;
309 }
310
311 ClustersNeeded -= AssignedClusters;
312 LastClusterInDataRun.LowPart = NextAssignedCluster + AssignedClusters - 1;
313 }
314 }
315 else if (AttrContext->Record.NonResident.AllocatedSize > AllocationSize)
316 {
317 // shrink allocation size
318 ULONG ClustersToFree = ExistingClusters - (AllocationSize / BytesPerCluster);
319 Status = FreeClusters(Fcb->Vcb, AttrContext, AttrOffset, FileRecord, ClustersToFree);
320 }
321
322 // TODO: is the file compressed, encrypted, or sparse?
323
324 // NOTE: we need to have acquired the main resource exclusively, as well as(?) the PagingIoResource
325
326 Fcb->RFCB.AllocationSize.QuadPart = AllocationSize;
327 AttrContext->Record.NonResident.AllocatedSize = AllocationSize;
328 AttrContext->Record.NonResident.DataSize = DataSize->QuadPart;
329 AttrContext->Record.NonResident.InitializedSize = DataSize->QuadPart;
330
331 DestinationAttribute->NonResident.AllocatedSize = AllocationSize;
332 DestinationAttribute->NonResident.DataSize = DataSize->QuadPart;
333 DestinationAttribute->NonResident.InitializedSize = DataSize->QuadPart;
334
335 DPRINT("Allocated Size: %I64u\n", DestinationAttribute->NonResident.AllocatedSize);
336 }
337 else
338 {
339 // resident attribute
340
341 // find the next attribute
342 ULONG NextAttributeOffset = AttrOffset + AttrContext->Record.Length;
343 PNTFS_ATTR_RECORD NextAttribute = (PNTFS_ATTR_RECORD)((PCHAR)FileRecord + NextAttributeOffset);
344
345 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
346
347 // Do we need to increase the data length?
348 if (DataSize->QuadPart > AttrContext->Record.Resident.ValueLength)
349 {
350 // There's usually padding at the end of a record. Do we need to extend past it?
351 ULONG MaxValueLength = AttrContext->Record.Length - AttrContext->Record.Resident.ValueOffset;
352 if (MaxValueLength < DataSize->LowPart)
353 {
354 // If this is the last attribute, we could move the end marker to the very end of the file record
355 MaxValueLength += Fcb->Vcb->NtfsInfo.BytesPerFileRecord - NextAttributeOffset - (sizeof(ULONG) * 2);
356
357 if (MaxValueLength < DataSize->LowPart || NextAttribute->Type != AttributeEnd)
358 {
359 // convert attribute to non-resident
360 PNTFS_ATTR_RECORD Destination = (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + AttrOffset);
361 LARGE_INTEGER AttribDataSize;
362 PVOID AttribData;
363 ULONG EndAttributeOffset;
364 ULONG LengthWritten;
365
366 DPRINT1("Converting attribute to non-resident.\n");
367
368 AttribDataSize.QuadPart = AttrContext->Record.Resident.ValueLength;
369
370 // Is there existing data we need to back-up?
371 if (AttribDataSize.QuadPart > 0)
372 {
373 AttribData = ExAllocatePoolWithTag(NonPagedPool, AttribDataSize.QuadPart, TAG_NTFS);
374 if (AttribData == NULL)
375 {
376 DPRINT1("ERROR: Couldn't allocate memory for attribute data. Can't migrate to non-resident!\n");
377 return STATUS_INSUFFICIENT_RESOURCES;
378 }
379
380 // read data to temp buffer
381 Status = ReadAttribute(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart);
382 if (!NT_SUCCESS(Status))
383 {
384 DPRINT1("ERROR: Unable to read attribute before migrating!\n");
385 ExFreePoolWithTag(AttribData, TAG_NTFS);
386 return Status;
387 }
388 }
389
390 // Start by turning this attribute into a 0-length, non-resident attribute, then enlarge it.
391
392 // Zero out the NonResident structure
393 RtlZeroMemory(&AttrContext->Record.NonResident.LowestVCN,
394 FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
395 RtlZeroMemory(&Destination->NonResident.LowestVCN,
396 FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.CompressedSize) - FIELD_OFFSET(NTFS_ATTR_RECORD, NonResident.LowestVCN));
397
398 // update the mapping pairs offset, which will be 0x40 + length in bytes of the name
399 AttrContext->Record.NonResident.MappingPairsOffset = Destination->NonResident.MappingPairsOffset = 0x40 + (Destination->NameLength * 2);
400
401 // mark the attribute as non-resident
402 AttrContext->Record.IsNonResident = Destination->IsNonResident = 1;
403
404 // update the end of the file record
405 // calculate position of end markers (1 byte for empty data run)
406 EndAttributeOffset = AttrOffset + AttrContext->Record.NonResident.MappingPairsOffset + 1;
407 EndAttributeOffset = ALIGN_UP_BY(EndAttributeOffset, 8);
408
409 // Update the length
410 Destination->Length = EndAttributeOffset - AttrOffset;
411 AttrContext->Record.Length = Destination->Length;
412
413 // Update the file record end
414 SetFileRecordEnd(FileRecord,
415 (PNTFS_ATTR_RECORD)((ULONG_PTR)FileRecord + EndAttributeOffset),
416 FILE_RECORD_END);
417
418 // update file record on disk
419 Status = UpdateFileRecord(Fcb->Vcb, AttrContext->FileMFTIndex, FileRecord);
420 if (!NT_SUCCESS(Status))
421 {
422 DPRINT1("ERROR: Couldn't update file record to continue migration!\n");
423 if (AttribDataSize.QuadPart > 0)
424 ExFreePoolWithTag(AttribData, TAG_NTFS);
425 return Status;
426 }
427
428 // Initialize the MCB, potentially catch an exception
429 _SEH2_TRY{
430 FsRtlInitializeLargeMcb(&AttrContext->DataRunsMCB, NonPagedPool);
431 } _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
432 _SEH2_YIELD(return _SEH2_GetExceptionCode());
433 } _SEH2_END;
434
435 // Now we can treat the attribute as non-resident and enlarge it normally
436 Status = SetAttributeDataLength(FileObject, Fcb, AttrContext, AttrOffset, FileRecord, DataSize);
437 if (!NT_SUCCESS(Status))
438 {
439 DPRINT1("ERROR: Unable to migrate resident attribute!\n");
440 if(AttribData != NULL)
441 ExFreePoolWithTag(AttribData, TAG_NTFS);
442 return Status;
443 }
444
445 // restore the back-up attribute, if we made one
446 if (AttribDataSize.QuadPart > 0)
447 {
448 Status = WriteAttribute(Fcb->Vcb, AttrContext, 0, AttribData, AttribDataSize.QuadPart, &LengthWritten);
449 if (!NT_SUCCESS(Status))
450 {
451 DPRINT1("ERROR: Unable to write attribute data to non-resident clusters during migration!\n");
452 // TODO: Reverse migration so no data is lost
453 ExFreePoolWithTag(AttribData, TAG_NTFS);
454 return Status;
455 }
456
457 ExFreePoolWithTag(AttribData, TAG_NTFS);
458 }
459 }
460 }
461 }
462 else if (DataSize->LowPart < AttrContext->Record.Resident.ValueLength)
463 {
464 // we need to decrease the length
465 if (NextAttribute->Type != AttributeEnd)
466 {
467 DPRINT1("FIXME: Don't know how to decrease length of resident attribute unless it's the final attribute!\n");
468 return STATUS_NOT_IMPLEMENTED;
469 }
470 }
471
472 // set the new length of the resident attribute (if we didn't migrate it)
473 if(!AttrContext->Record.IsNonResident)
474 InternalSetResidentAttributeLength(AttrContext, FileRecord, AttrOffset, DataSize->LowPart);
475 }
476
477 //NtfsDumpFileAttributes(Fcb->Vcb, FileRecord);
478
479 // write the updated file record back to disk
480 Status = UpdateFileRecord(Fcb->Vcb, Fcb->MFTIndex, FileRecord);
481
482 if (NT_SUCCESS(Status))
483 {
484 Fcb->RFCB.FileSize = *DataSize;
485 Fcb->RFCB.ValidDataLength = *DataSize;
486 CcSetFileSizes(FileObject, (PCC_FILE_SIZES)&Fcb->RFCB.AllocationSize);
487 }
488
489 return STATUS_SUCCESS;
490 }
491
492 /**
493 * @name SetFileRecordEnd
494 * @implemented
495 *
496 * This small function sets a new endpoint for the file record. It set's the final
497 * AttrEnd->Type to AttributeEnd and recalculates the bytes used by the file record.
498 *
499 * @param FileRecord
500 * Pointer to the file record whose endpoint (length) will be set.
501 *
502 * @param AttrEnd
503 * Pointer to section of memory that will receive the AttributeEnd marker. This must point
504 * to memory allocated for the FileRecord. Must be aligned to an 8-byte boundary (relative to FileRecord).
505 *
506 * @param EndMarker
507 * This value will be written after AttributeEnd but isn't critical at all. When Windows resizes
508 * a file record, it preserves the final ULONG that previously ended the record, even though this
509 * value is (to my knowledge) never used. We emulate this behavior.
510 *
511 */
512 VOID
513 SetFileRecordEnd(PFILE_RECORD_HEADER FileRecord,
514 PNTFS_ATTR_RECORD AttrEnd,
515 ULONG EndMarker)
516 {
517 // mark the end of attributes
518 AttrEnd->Type = AttributeEnd;
519
520 // Restore the "file-record-end marker." The value is never checked but this behavior is consistent with Win2k3.
521 AttrEnd->Length = EndMarker;
522
523 // recalculate bytes in use
524 FileRecord->BytesInUse = (ULONG_PTR)AttrEnd - (ULONG_PTR)FileRecord + sizeof(ULONG) * 2;
525 }
526
527 ULONG
528 ReadAttribute(PDEVICE_EXTENSION Vcb,
529 PNTFS_ATTR_CONTEXT Context,
530 ULONGLONG Offset,
531 PCHAR Buffer,
532 ULONG Length)
533 {
534 ULONGLONG LastLCN;
535 PUCHAR DataRun;
536 LONGLONG DataRunOffset;
537 ULONGLONG DataRunLength;
538 LONGLONG DataRunStartLCN;
539 ULONGLONG CurrentOffset;
540 ULONG ReadLength;
541 ULONG AlreadyRead;
542 NTSTATUS Status;
543
544 //TEMPTEMP
545 PUCHAR TempBuffer;
546
547 if (!Context->Record.IsNonResident)
548 {
549 if (Offset > Context->Record.Resident.ValueLength)
550 return 0;
551 if (Offset + Length > Context->Record.Resident.ValueLength)
552 Length = (ULONG)(Context->Record.Resident.ValueLength - Offset);
553 RtlCopyMemory(Buffer, (PCHAR)&Context->Record + Context->Record.Resident.ValueOffset + Offset, Length);
554 return Length;
555 }
556
557 /*
558 * Non-resident attribute
559 */
560
561 /*
562 * I. Find the corresponding start data run.
563 */
564
565 AlreadyRead = 0;
566
567 // FIXME: Cache seems to be non-working. Disable it for now
568 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
569 if (0)
570 {
571 DataRun = Context->CacheRun;
572 LastLCN = Context->CacheRunLastLCN;
573 DataRunStartLCN = Context->CacheRunStartLCN;
574 DataRunLength = Context->CacheRunLength;
575 CurrentOffset = Context->CacheRunCurrentOffset;
576 }
577 else
578 {
579 //TEMPTEMP
580 ULONG UsedBufferSize;
581 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
582
583 LastLCN = 0;
584 CurrentOffset = 0;
585
586 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
587 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
588 TempBuffer,
589 Vcb->NtfsInfo.BytesPerFileRecord,
590 &UsedBufferSize);
591
592 DataRun = TempBuffer;
593
594 while (1)
595 {
596 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
597 if (DataRunOffset != -1)
598 {
599 /* Normal data run. */
600 DataRunStartLCN = LastLCN + DataRunOffset;
601 LastLCN = DataRunStartLCN;
602 }
603 else
604 {
605 /* Sparse data run. */
606 DataRunStartLCN = -1;
607 }
608
609 if (Offset >= CurrentOffset &&
610 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
611 {
612 break;
613 }
614
615 if (*DataRun == 0)
616 {
617 return AlreadyRead;
618 }
619
620 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
621 }
622 }
623
624 /*
625 * II. Go through the run list and read the data
626 */
627
628 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
629 if (DataRunStartLCN == -1)
630 {
631 RtlZeroMemory(Buffer, ReadLength);
632 Status = STATUS_SUCCESS;
633 }
634 else
635 {
636 Status = NtfsReadDisk(Vcb->StorageDevice,
637 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset,
638 ReadLength,
639 Vcb->NtfsInfo.BytesPerSector,
640 (PVOID)Buffer,
641 FALSE);
642 }
643 if (NT_SUCCESS(Status))
644 {
645 Length -= ReadLength;
646 Buffer += ReadLength;
647 AlreadyRead += ReadLength;
648
649 if (ReadLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
650 {
651 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
652 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
653 if (DataRunOffset != (ULONGLONG)-1)
654 {
655 DataRunStartLCN = LastLCN + DataRunOffset;
656 LastLCN = DataRunStartLCN;
657 }
658 else
659 DataRunStartLCN = -1;
660 }
661
662 while (Length > 0)
663 {
664 ReadLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
665 if (DataRunStartLCN == -1)
666 RtlZeroMemory(Buffer, ReadLength);
667 else
668 {
669 Status = NtfsReadDisk(Vcb->StorageDevice,
670 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
671 ReadLength,
672 Vcb->NtfsInfo.BytesPerSector,
673 (PVOID)Buffer,
674 FALSE);
675 if (!NT_SUCCESS(Status))
676 break;
677 }
678
679 Length -= ReadLength;
680 Buffer += ReadLength;
681 AlreadyRead += ReadLength;
682
683 /* We finished this request, but there still data in this data run. */
684 if (Length == 0 && ReadLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
685 break;
686
687 /*
688 * Go to next run in the list.
689 */
690
691 if (*DataRun == 0)
692 break;
693 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
694 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
695 if (DataRunOffset != -1)
696 {
697 /* Normal data run. */
698 DataRunStartLCN = LastLCN + DataRunOffset;
699 LastLCN = DataRunStartLCN;
700 }
701 else
702 {
703 /* Sparse data run. */
704 DataRunStartLCN = -1;
705 }
706 } /* while */
707
708 } /* if Disk */
709
710 // TEMPTEMP
711 if (Context->Record.IsNonResident)
712 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
713
714 Context->CacheRun = DataRun;
715 Context->CacheRunOffset = Offset + AlreadyRead;
716 Context->CacheRunStartLCN = DataRunStartLCN;
717 Context->CacheRunLength = DataRunLength;
718 Context->CacheRunLastLCN = LastLCN;
719 Context->CacheRunCurrentOffset = CurrentOffset;
720
721 return AlreadyRead;
722 }
723
724
725 /**
726 * @name WriteAttribute
727 * @implemented
728 *
729 * Writes an NTFS attribute to the disk. It presently borrows a lot of code from ReadAttribute(),
730 * and it still needs more documentation / cleaning up.
731 *
732 * @param Vcb
733 * Volume Control Block indicating which volume to write the attribute to
734 *
735 * @param Context
736 * Pointer to an NTFS_ATTR_CONTEXT that has information about the attribute
737 *
738 * @param Offset
739 * Offset, in bytes, from the beginning of the attribute indicating where to start
740 * writing data
741 *
742 * @param Buffer
743 * The data that's being written to the device
744 *
745 * @param Length
746 * How much data will be written, in bytes
747 *
748 * @param RealLengthWritten
749 * Pointer to a ULONG which will receive how much data was written, in bytes
750 *
751 * @return
752 * STATUS_SUCCESS if successful, an error code otherwise. STATUS_NOT_IMPLEMENTED if
753 * writing to a sparse file.
754 *
755 * @remarks Note that in this context the word "attribute" isn't referring read-only, hidden,
756 * etc. - the file's data is actually stored in an attribute in NTFS parlance.
757 *
758 */
759
760 NTSTATUS
761 WriteAttribute(PDEVICE_EXTENSION Vcb,
762 PNTFS_ATTR_CONTEXT Context,
763 ULONGLONG Offset,
764 const PUCHAR Buffer,
765 ULONG Length,
766 PULONG RealLengthWritten)
767 {
768 ULONGLONG LastLCN;
769 PUCHAR DataRun;
770 LONGLONG DataRunOffset;
771 ULONGLONG DataRunLength;
772 LONGLONG DataRunStartLCN;
773 ULONGLONG CurrentOffset;
774 ULONG WriteLength;
775 NTSTATUS Status;
776 PUCHAR SourceBuffer = Buffer;
777 LONGLONG StartingOffset;
778
779 //TEMPTEMP
780 PUCHAR TempBuffer;
781
782
783 DPRINT("WriteAttribute(%p, %p, %I64u, %p, %lu, %p)\n", Vcb, Context, Offset, Buffer, Length, RealLengthWritten);
784
785 *RealLengthWritten = 0;
786
787 // is this a resident attribute?
788 if (!Context->Record.IsNonResident)
789 {
790 ULONG AttributeOffset;
791 PNTFS_ATTR_CONTEXT FoundContext;
792 PFILE_RECORD_HEADER FileRecord;
793
794 if (Offset + Length > Context->Record.Resident.ValueLength)
795 {
796 DPRINT1("DRIVER ERROR: Attribute is too small!\n");
797 return STATUS_INVALID_PARAMETER;
798 }
799
800 FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
801
802 if (!FileRecord)
803 {
804 DPRINT1("Error: Couldn't allocate file record!\n");
805 return STATUS_NO_MEMORY;
806 }
807
808 // read the file record
809 ReadFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
810
811 // find where to write the attribute data to
812 Status = FindAttribute(Vcb, FileRecord,
813 Context->Record.Type,
814 (PCWSTR)((PCHAR)&Context->Record + Context->Record.NameOffset),
815 Context->Record.NameLength,
816 &FoundContext,
817 &AttributeOffset);
818
819 if (!NT_SUCCESS(Status))
820 {
821 DPRINT1("ERROR: Couldn't find matching attribute!\n");
822 ExFreePoolWithTag(FileRecord, TAG_NTFS);
823 return Status;
824 }
825
826 DPRINT("Offset: %I64u, AttributeOffset: %u, ValueOffset: %u\n", Offset, AttributeOffset, Context->Record.Resident.ValueLength);
827 Offset += AttributeOffset + Context->Record.Resident.ValueOffset;
828
829 if (Offset + Length > Vcb->NtfsInfo.BytesPerFileRecord)
830 {
831 DPRINT1("DRIVER ERROR: Data being written extends past end of file record!\n");
832 ReleaseAttributeContext(FoundContext);
833 ExFreePoolWithTag(FileRecord, TAG_NTFS);
834 return STATUS_INVALID_PARAMETER;
835 }
836
837 // copy the data being written into the file record
838 RtlCopyMemory((PCHAR)FileRecord + Offset, Buffer, Length);
839
840 Status = UpdateFileRecord(Vcb, Context->FileMFTIndex, FileRecord);
841
842 ReleaseAttributeContext(FoundContext);
843 ExFreePoolWithTag(FileRecord, TAG_NTFS);
844
845 if (NT_SUCCESS(Status))
846 *RealLengthWritten = Length;
847
848 return Status;
849 }
850
851 // This is a non-resident attribute.
852
853 // I. Find the corresponding start data run.
854
855 // FIXME: Cache seems to be non-working. Disable it for now
856 //if(Context->CacheRunOffset <= Offset && Offset < Context->CacheRunOffset + Context->CacheRunLength * Volume->ClusterSize)
857 /*if (0)
858 {
859 DataRun = Context->CacheRun;
860 LastLCN = Context->CacheRunLastLCN;
861 DataRunStartLCN = Context->CacheRunStartLCN;
862 DataRunLength = Context->CacheRunLength;
863 CurrentOffset = Context->CacheRunCurrentOffset;
864 }
865 else*/
866 {
867 ULONG UsedBufferSize;
868 LastLCN = 0;
869 CurrentOffset = 0;
870
871 // This will be rewritten in the next iteration to just use the DataRuns MCB directly
872 TempBuffer = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
873
874 ConvertLargeMCBToDataRuns(&Context->DataRunsMCB,
875 TempBuffer,
876 Vcb->NtfsInfo.BytesPerFileRecord,
877 &UsedBufferSize);
878
879 DataRun = TempBuffer;
880
881 while (1)
882 {
883 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
884 if (DataRunOffset != -1)
885 {
886 // Normal data run.
887 // DPRINT1("Writing to normal data run, LastLCN %I64u DataRunOffset %I64d\n", LastLCN, DataRunOffset);
888 DataRunStartLCN = LastLCN + DataRunOffset;
889 LastLCN = DataRunStartLCN;
890 }
891 else
892 {
893 // Sparse data run. We can't support writing to sparse files yet
894 // (it may require increasing the allocation size).
895 DataRunStartLCN = -1;
896 DPRINT1("FIXME: Writing to sparse files is not supported yet!\n");
897 return STATUS_NOT_IMPLEMENTED;
898 }
899
900 // Have we reached the data run we're trying to write to?
901 if (Offset >= CurrentOffset &&
902 Offset < CurrentOffset + (DataRunLength * Vcb->NtfsInfo.BytesPerCluster))
903 {
904 break;
905 }
906
907 if (*DataRun == 0)
908 {
909 // We reached the last assigned cluster
910 // TODO: assign new clusters to the end of the file.
911 // (Presently, this code will rarely be reached, the write will usually have already failed by now)
912 // [We can reach here by creating a new file record when the MFT isn't large enough]
913 DPRINT1("FIXME: Master File Table needs to be enlarged.\n");
914 return STATUS_END_OF_FILE;
915 }
916
917 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
918 }
919 }
920
921 // II. Go through the run list and write the data
922
923 /* REVIEWME -- As adapted from NtfsReadAttribute():
924 We seem to be making a special case for the first applicable data run, but I'm not sure why.
925 Does it have something to do with (not) caching? Is this strategy equally applicable to writing? */
926
927 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset), Length);
928
929 StartingOffset = DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster + Offset - CurrentOffset;
930
931 // Write the data to the disk
932 Status = NtfsWriteDisk(Vcb->StorageDevice,
933 StartingOffset,
934 WriteLength,
935 Vcb->NtfsInfo.BytesPerSector,
936 (PVOID)SourceBuffer);
937
938 // Did the write fail?
939 if (!NT_SUCCESS(Status))
940 {
941 Context->CacheRun = DataRun;
942 Context->CacheRunOffset = Offset;
943 Context->CacheRunStartLCN = DataRunStartLCN;
944 Context->CacheRunLength = DataRunLength;
945 Context->CacheRunLastLCN = LastLCN;
946 Context->CacheRunCurrentOffset = CurrentOffset;
947
948 return Status;
949 }
950
951 Length -= WriteLength;
952 SourceBuffer += WriteLength;
953 *RealLengthWritten += WriteLength;
954
955 // Did we write to the end of the data run?
956 if (WriteLength == DataRunLength * Vcb->NtfsInfo.BytesPerCluster - (Offset - CurrentOffset))
957 {
958 // Advance to the next data run
959 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
960 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
961
962 if (DataRunOffset != (ULONGLONG)-1)
963 {
964 DataRunStartLCN = LastLCN + DataRunOffset;
965 LastLCN = DataRunStartLCN;
966 }
967 else
968 DataRunStartLCN = -1;
969 }
970
971 // Do we have more data to write?
972 while (Length > 0)
973 {
974 // Make sure we don't write past the end of the current data run
975 WriteLength = (ULONG)min(DataRunLength * Vcb->NtfsInfo.BytesPerCluster, Length);
976
977 // Are we dealing with a sparse data run?
978 if (DataRunStartLCN == -1)
979 {
980 DPRINT1("FIXME: Don't know how to write to sparse files yet! (DataRunStartLCN == -1)\n");
981 return STATUS_NOT_IMPLEMENTED;
982 }
983 else
984 {
985 // write the data to the disk
986 Status = NtfsWriteDisk(Vcb->StorageDevice,
987 DataRunStartLCN * Vcb->NtfsInfo.BytesPerCluster,
988 WriteLength,
989 Vcb->NtfsInfo.BytesPerSector,
990 (PVOID)SourceBuffer);
991 if (!NT_SUCCESS(Status))
992 break;
993 }
994
995 Length -= WriteLength;
996 SourceBuffer += WriteLength;
997 *RealLengthWritten += WriteLength;
998
999 // We finished this request, but there's still data in this data run.
1000 if (Length == 0 && WriteLength != DataRunLength * Vcb->NtfsInfo.BytesPerCluster)
1001 break;
1002
1003 // Go to next run in the list.
1004
1005 if (*DataRun == 0)
1006 {
1007 // that was the last run
1008 if (Length > 0)
1009 {
1010 // Failed sanity check.
1011 DPRINT1("Encountered EOF before expected!\n");
1012 return STATUS_END_OF_FILE;
1013 }
1014
1015 break;
1016 }
1017
1018 // Advance to the next data run
1019 CurrentOffset += DataRunLength * Vcb->NtfsInfo.BytesPerCluster;
1020 DataRun = DecodeRun(DataRun, &DataRunOffset, &DataRunLength);
1021 if (DataRunOffset != -1)
1022 {
1023 // Normal data run.
1024 DataRunStartLCN = LastLCN + DataRunOffset;
1025 LastLCN = DataRunStartLCN;
1026 }
1027 else
1028 {
1029 // Sparse data run.
1030 DataRunStartLCN = -1;
1031 }
1032 } // end while (Length > 0) [more data to write]
1033
1034 // TEMPTEMP
1035 if(Context->Record.IsNonResident)
1036 ExFreePoolWithTag(TempBuffer, TAG_NTFS);
1037
1038 Context->CacheRun = DataRun;
1039 Context->CacheRunOffset = Offset + *RealLengthWritten;
1040 Context->CacheRunStartLCN = DataRunStartLCN;
1041 Context->CacheRunLength = DataRunLength;
1042 Context->CacheRunLastLCN = LastLCN;
1043 Context->CacheRunCurrentOffset = CurrentOffset;
1044
1045 return Status;
1046 }
1047
1048 NTSTATUS
1049 ReadFileRecord(PDEVICE_EXTENSION Vcb,
1050 ULONGLONG index,
1051 PFILE_RECORD_HEADER file)
1052 {
1053 ULONGLONG BytesRead;
1054
1055 DPRINT("ReadFileRecord(%p, %I64x, %p)\n", Vcb, index, file);
1056
1057 BytesRead = ReadAttribute(Vcb, Vcb->MFTContext, index * Vcb->NtfsInfo.BytesPerFileRecord, (PCHAR)file, Vcb->NtfsInfo.BytesPerFileRecord);
1058 if (BytesRead != Vcb->NtfsInfo.BytesPerFileRecord)
1059 {
1060 DPRINT1("ReadFileRecord failed: %I64u read, %u expected\n", BytesRead, Vcb->NtfsInfo.BytesPerFileRecord);
1061 return STATUS_PARTIAL_COPY;
1062 }
1063
1064 /* Apply update sequence array fixups. */
1065 DPRINT("Sequence number: %u\n", file->SequenceNumber);
1066 return FixupUpdateSequenceArray(Vcb, &file->Ntfs);
1067 }
1068
1069
1070 /**
1071 * Searches a file's parent directory (given the parent's index in the mft)
1072 * for the given file. Upon finding an index entry for that file, updates
1073 * Data Size and Allocated Size values in the $FILE_NAME attribute of that entry.
1074 *
1075 * (Most of this code was copied from NtfsFindMftRecord)
1076 */
1077 NTSTATUS
1078 UpdateFileNameRecord(PDEVICE_EXTENSION Vcb,
1079 ULONGLONG ParentMFTIndex,
1080 PUNICODE_STRING FileName,
1081 BOOLEAN DirSearch,
1082 ULONGLONG NewDataSize,
1083 ULONGLONG NewAllocationSize)
1084 {
1085 PFILE_RECORD_HEADER MftRecord;
1086 PNTFS_ATTR_CONTEXT IndexRootCtx;
1087 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1088 PCHAR IndexRecord;
1089 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1090 NTSTATUS Status;
1091 ULONG CurrentEntry = 0;
1092
1093 DPRINT("UpdateFileNameRecord(%p, %I64d, %wZ, %u, %I64u, %I64u)\n", Vcb, ParentMFTIndex, FileName, DirSearch, NewDataSize, NewAllocationSize);
1094
1095 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1096 Vcb->NtfsInfo.BytesPerFileRecord,
1097 TAG_NTFS);
1098 if (MftRecord == NULL)
1099 {
1100 return STATUS_INSUFFICIENT_RESOURCES;
1101 }
1102
1103 Status = ReadFileRecord(Vcb, ParentMFTIndex, MftRecord);
1104 if (!NT_SUCCESS(Status))
1105 {
1106 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1107 return Status;
1108 }
1109
1110 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1111 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1112 if (!NT_SUCCESS(Status))
1113 {
1114 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1115 return Status;
1116 }
1117
1118 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1119 if (IndexRecord == NULL)
1120 {
1121 ReleaseAttributeContext(IndexRootCtx);
1122 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1123 return STATUS_INSUFFICIENT_RESOURCES;
1124 }
1125
1126 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
1127 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1128 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1129 // Index root is always resident.
1130 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1131
1132 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1133
1134 Status = UpdateIndexEntryFileNameSize(Vcb,
1135 MftRecord,
1136 IndexRecord,
1137 IndexRoot->SizeOfEntry,
1138 IndexEntry,
1139 IndexEntryEnd,
1140 FileName,
1141 &CurrentEntry,
1142 &CurrentEntry,
1143 DirSearch,
1144 NewDataSize,
1145 NewAllocationSize);
1146
1147 ReleaseAttributeContext(IndexRootCtx);
1148 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1149 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1150
1151 return Status;
1152 }
1153
1154 /**
1155 * Recursively searches directory index and applies the size update to the $FILE_NAME attribute of the
1156 * proper index entry.
1157 * (Heavily based on BrowseIndexEntries)
1158 */
1159 NTSTATUS
1160 UpdateIndexEntryFileNameSize(PDEVICE_EXTENSION Vcb,
1161 PFILE_RECORD_HEADER MftRecord,
1162 PCHAR IndexRecord,
1163 ULONG IndexBlockSize,
1164 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1165 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1166 PUNICODE_STRING FileName,
1167 PULONG StartEntry,
1168 PULONG CurrentEntry,
1169 BOOLEAN DirSearch,
1170 ULONGLONG NewDataSize,
1171 ULONGLONG NewAllocatedSize)
1172 {
1173 NTSTATUS Status;
1174 ULONG RecordOffset;
1175 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1176 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1177 ULONGLONG IndexAllocationSize;
1178 PINDEX_BUFFER IndexBuffer;
1179
1180 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);
1181
1182 // find the index entry responsible for the file we're trying to update
1183 IndexEntry = FirstEntry;
1184 while (IndexEntry < LastEntry &&
1185 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1186 {
1187 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
1188 *CurrentEntry >= *StartEntry &&
1189 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1190 CompareFileName(FileName, IndexEntry, DirSearch))
1191 {
1192 *StartEntry = *CurrentEntry;
1193 IndexEntry->FileName.DataSize = NewDataSize;
1194 IndexEntry->FileName.AllocatedSize = NewAllocatedSize;
1195 // indicate that the caller will still need to write the structure to the disk
1196 return STATUS_PENDING;
1197 }
1198
1199 (*CurrentEntry) += 1;
1200 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1201 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1202 }
1203
1204 /* If we're already browsing a subnode */
1205 if (IndexRecord == NULL)
1206 {
1207 return STATUS_OBJECT_PATH_NOT_FOUND;
1208 }
1209
1210 /* If there's no subnode */
1211 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1212 {
1213 return STATUS_OBJECT_PATH_NOT_FOUND;
1214 }
1215
1216 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1217 if (!NT_SUCCESS(Status))
1218 {
1219 DPRINT("Corrupted filesystem!\n");
1220 return Status;
1221 }
1222
1223 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1224 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1225 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1226 {
1227 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1228 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1229 if (!NT_SUCCESS(Status))
1230 {
1231 break;
1232 }
1233
1234 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1235 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1236 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1237 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1238 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1239 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1240
1241 Status = UpdateIndexEntryFileNameSize(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, NewDataSize, NewAllocatedSize);
1242 if (Status == STATUS_PENDING)
1243 {
1244 // write the index record back to disk
1245 ULONG Written;
1246
1247 // first we need to update the fixup values for the index block
1248 Status = AddFixupArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1249 if (!NT_SUCCESS(Status))
1250 {
1251 DPRINT1("Error: Failed to update fixup sequence array!\n");
1252 break;
1253 }
1254
1255 Status = WriteAttribute(Vcb, IndexAllocationCtx, RecordOffset, (const PUCHAR)IndexRecord, IndexBlockSize, &Written);
1256 if (!NT_SUCCESS(Status))
1257 {
1258 DPRINT1("ERROR Performing write!\n");
1259 break;
1260 }
1261
1262 Status = STATUS_SUCCESS;
1263 break;
1264 }
1265 if (NT_SUCCESS(Status))
1266 {
1267 break;
1268 }
1269 }
1270
1271 ReleaseAttributeContext(IndexAllocationCtx);
1272 return Status;
1273 }
1274
1275 /**
1276 * @name UpdateFileRecord
1277 * @implemented
1278 *
1279 * Writes a file record to the master file table, at a given index.
1280 *
1281 * @param Vcb
1282 * Pointer to the DEVICE_EXTENSION of the target drive being written to.
1283 *
1284 * @param MftIndex
1285 * Target index in the master file table to store the file record.
1286 *
1287 * @param FileRecord
1288 * Pointer to the complete file record which will be written to the master file table.
1289 *
1290 * @return
1291 * STATUS_SUCCESSFUL on success. An error passed from WriteAttribute() otherwise.
1292 *
1293 */
1294 NTSTATUS
1295 UpdateFileRecord(PDEVICE_EXTENSION Vcb,
1296 ULONGLONG MftIndex,
1297 PFILE_RECORD_HEADER FileRecord)
1298 {
1299 ULONG BytesWritten;
1300 NTSTATUS Status = STATUS_SUCCESS;
1301
1302 DPRINT("UpdateFileRecord(%p, 0x%I64x, %p)\n", Vcb, MftIndex, FileRecord);
1303
1304 // Add the fixup array to prepare the data for writing to disk
1305 AddFixupArray(Vcb, &FileRecord->Ntfs);
1306
1307 // write the file record to the master file table
1308 Status = WriteAttribute(Vcb, Vcb->MFTContext, MftIndex * Vcb->NtfsInfo.BytesPerFileRecord, (const PUCHAR)FileRecord, Vcb->NtfsInfo.BytesPerFileRecord, &BytesWritten);
1309
1310 if (!NT_SUCCESS(Status))
1311 {
1312 DPRINT1("UpdateFileRecord failed: %lu written, %lu expected\n", BytesWritten, Vcb->NtfsInfo.BytesPerFileRecord);
1313 }
1314
1315 // remove the fixup array (so the file record pointer can still be used)
1316 FixupUpdateSequenceArray(Vcb, &FileRecord->Ntfs);
1317
1318 return Status;
1319 }
1320
1321
1322 NTSTATUS
1323 FixupUpdateSequenceArray(PDEVICE_EXTENSION Vcb,
1324 PNTFS_RECORD_HEADER Record)
1325 {
1326 USHORT *USA;
1327 USHORT USANumber;
1328 USHORT USACount;
1329 USHORT *Block;
1330
1331 USA = (USHORT*)((PCHAR)Record + Record->UsaOffset);
1332 USANumber = *(USA++);
1333 USACount = Record->UsaCount - 1; /* Exclude the USA Number. */
1334 Block = (USHORT*)((PCHAR)Record + Vcb->NtfsInfo.BytesPerSector - 2);
1335
1336 DPRINT("FixupUpdateSequenceArray(%p, %p)\nUSANumber: %u\tUSACount: %u\n", Vcb, Record, USANumber, USACount);
1337
1338 while (USACount)
1339 {
1340 if (*Block != USANumber)
1341 {
1342 DPRINT1("Mismatch with USA: %u read, %u expected\n" , *Block, USANumber);
1343 return STATUS_UNSUCCESSFUL;
1344 }
1345 *Block = *(USA++);
1346 Block = (USHORT*)((PCHAR)Block + Vcb->NtfsInfo.BytesPerSector);
1347 USACount--;
1348 }
1349
1350 return STATUS_SUCCESS;
1351 }
1352
1353 /**
1354 * @name AddNewMftEntry
1355 * @implemented
1356 *
1357 * Adds a file record to the master file table of a given device.
1358 *
1359 * @param FileRecord
1360 * Pointer to a complete file record which will be saved to disk.
1361 *
1362 * @param DeviceExt
1363 * Pointer to the DEVICE_EXTENSION of the target drive.
1364 *
1365 * @return
1366 * STATUS_SUCCESS on success.
1367 * STATUS_OBJECT_NAME_NOT_FOUND if we can't find the MFT's $Bitmap or if we weren't able
1368 * to read the attribute.
1369 * STATUS_INSUFFICIENT_RESOURCES if we can't allocate enough memory for a copy of $Bitmap.
1370 * STATUS_NOT_IMPLEMENTED if we need to increase the size of the MFT.
1371 *
1372 */
1373 NTSTATUS
1374 AddNewMftEntry(PFILE_RECORD_HEADER FileRecord,
1375 PDEVICE_EXTENSION DeviceExt)
1376 {
1377 NTSTATUS Status = STATUS_SUCCESS;
1378 ULONGLONG MftIndex;
1379 RTL_BITMAP Bitmap;
1380 ULONGLONG BitmapDataSize;
1381 ULONGLONG AttrBytesRead;
1382 PVOID BitmapData;
1383 ULONG LengthWritten;
1384
1385 // First, we have to read the mft's $Bitmap attribute
1386 PNTFS_ATTR_CONTEXT BitmapContext;
1387 Status = FindAttribute(DeviceExt, DeviceExt->MasterFileTable, AttributeBitmap, L"", 0, &BitmapContext, NULL);
1388 if (!NT_SUCCESS(Status))
1389 {
1390 DPRINT1("ERROR: Couldn't find $Bitmap attribute of master file table!\n");
1391 return Status;
1392 }
1393
1394 // allocate a buffer for the $Bitmap attribute
1395 BitmapDataSize = AttributeDataLength(&BitmapContext->Record);
1396 BitmapData = ExAllocatePoolWithTag(NonPagedPool, BitmapDataSize, TAG_NTFS);
1397 if (!BitmapData)
1398 {
1399 ReleaseAttributeContext(BitmapContext);
1400 return STATUS_INSUFFICIENT_RESOURCES;
1401 }
1402
1403 // read $Bitmap attribute
1404 AttrBytesRead = ReadAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize);
1405
1406 if (AttrBytesRead == 0)
1407 {
1408 DPRINT1("ERROR: Unable to read $Bitmap attribute of master file table!\n");
1409 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1410 ReleaseAttributeContext(BitmapContext);
1411 return STATUS_OBJECT_NAME_NOT_FOUND;
1412 }
1413
1414 // convert buffer into bitmap
1415 RtlInitializeBitMap(&Bitmap, (PULONG)BitmapData, BitmapDataSize * 8);
1416
1417 // set next available bit, preferrably after 23rd bit
1418 MftIndex = RtlFindClearBitsAndSet(&Bitmap, 1, 24);
1419 if ((LONG)MftIndex == -1)
1420 {
1421 DPRINT1("ERROR: Couldn't find free space in MFT for file record!\n");
1422
1423 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1424 ReleaseAttributeContext(BitmapContext);
1425
1426 // TODO: increase mft size
1427 return STATUS_NOT_IMPLEMENTED;
1428 }
1429
1430 DPRINT1("Creating file record at MFT index: %I64u\n", MftIndex);
1431
1432 // update file record with index
1433 FileRecord->MFTRecordNumber = MftIndex;
1434
1435 // [BitmapData should have been updated via RtlFindClearBitsAndSet()]
1436
1437 // write the bitmap back to the MFT's $Bitmap attribute
1438 Status = WriteAttribute(DeviceExt, BitmapContext, 0, BitmapData, BitmapDataSize, &LengthWritten);
1439 if (!NT_SUCCESS(Status))
1440 {
1441 DPRINT1("ERROR encountered when writing $Bitmap attribute!\n");
1442 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1443 ReleaseAttributeContext(BitmapContext);
1444 return Status;
1445 }
1446
1447 // update the file record (write it to disk)
1448 Status = UpdateFileRecord(DeviceExt, MftIndex, FileRecord);
1449
1450 if (!NT_SUCCESS(Status))
1451 {
1452 DPRINT1("ERROR: Unable to write file record!\n");
1453 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1454 ReleaseAttributeContext(BitmapContext);
1455 return Status;
1456 }
1457
1458 ExFreePoolWithTag(BitmapData, TAG_NTFS);
1459 ReleaseAttributeContext(BitmapContext);
1460
1461 return Status;
1462 }
1463
1464 NTSTATUS
1465 AddFixupArray(PDEVICE_EXTENSION Vcb,
1466 PNTFS_RECORD_HEADER Record)
1467 {
1468 USHORT *pShortToFixUp;
1469 unsigned int ArrayEntryCount = Record->UsaCount - 1;
1470 unsigned int Offset = Vcb->NtfsInfo.BytesPerSector - 2;
1471 int i;
1472
1473 PFIXUP_ARRAY fixupArray = (PFIXUP_ARRAY)((UCHAR*)Record + Record->UsaOffset);
1474
1475 DPRINT("AddFixupArray(%p, %p)\n fixupArray->USN: %u, ArrayEntryCount: %u\n", Vcb, Record, fixupArray->USN, ArrayEntryCount);
1476
1477 fixupArray->USN++;
1478
1479 for (i = 0; i < ArrayEntryCount; i++)
1480 {
1481 DPRINT("USN: %u\tOffset: %u\n", fixupArray->USN, Offset);
1482
1483 pShortToFixUp = (USHORT*)((PCHAR)Record + Offset);
1484 fixupArray->Array[i] = *pShortToFixUp;
1485 *pShortToFixUp = fixupArray->USN;
1486 Offset += Vcb->NtfsInfo.BytesPerSector;
1487 }
1488
1489 return STATUS_SUCCESS;
1490 }
1491
1492 NTSTATUS
1493 ReadLCN(PDEVICE_EXTENSION Vcb,
1494 ULONGLONG lcn,
1495 ULONG count,
1496 PVOID buffer)
1497 {
1498 LARGE_INTEGER DiskSector;
1499
1500 DiskSector.QuadPart = lcn;
1501
1502 return NtfsReadSectors(Vcb->StorageDevice,
1503 DiskSector.u.LowPart * Vcb->NtfsInfo.SectorsPerCluster,
1504 count * Vcb->NtfsInfo.SectorsPerCluster,
1505 Vcb->NtfsInfo.BytesPerSector,
1506 buffer,
1507 FALSE);
1508 }
1509
1510
1511 BOOLEAN
1512 CompareFileName(PUNICODE_STRING FileName,
1513 PINDEX_ENTRY_ATTRIBUTE IndexEntry,
1514 BOOLEAN DirSearch)
1515 {
1516 BOOLEAN Ret, Alloc = FALSE;
1517 UNICODE_STRING EntryName;
1518
1519 EntryName.Buffer = IndexEntry->FileName.Name;
1520 EntryName.Length =
1521 EntryName.MaximumLength = IndexEntry->FileName.NameLength * sizeof(WCHAR);
1522
1523 if (DirSearch)
1524 {
1525 UNICODE_STRING IntFileName;
1526 if (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)
1527 {
1528 NT_VERIFY(NT_SUCCESS(RtlUpcaseUnicodeString(&IntFileName, FileName, TRUE)));
1529 Alloc = TRUE;
1530 }
1531 else
1532 {
1533 IntFileName = *FileName;
1534 }
1535
1536 Ret = FsRtlIsNameInExpression(&IntFileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX), NULL);
1537
1538 if (Alloc)
1539 {
1540 RtlFreeUnicodeString(&IntFileName);
1541 }
1542
1543 return Ret;
1544 }
1545 else
1546 {
1547 return (RtlCompareUnicodeString(FileName, &EntryName, (IndexEntry->FileName.NameType != NTFS_FILE_NAME_POSIX)) == 0);
1548 }
1549 }
1550
1551 #if 0
1552 static
1553 VOID
1554 DumpIndexEntry(PINDEX_ENTRY_ATTRIBUTE IndexEntry)
1555 {
1556 DPRINT1("Entry: %p\n", IndexEntry);
1557 DPRINT1("\tData.Directory.IndexedFile: %I64x\n", IndexEntry->Data.Directory.IndexedFile);
1558 DPRINT1("\tLength: %u\n", IndexEntry->Length);
1559 DPRINT1("\tKeyLength: %u\n", IndexEntry->KeyLength);
1560 DPRINT1("\tFlags: %x\n", IndexEntry->Flags);
1561 DPRINT1("\tReserved: %x\n", IndexEntry->Reserved);
1562 DPRINT1("\t\tDirectoryFileReferenceNumber: %I64x\n", IndexEntry->FileName.DirectoryFileReferenceNumber);
1563 DPRINT1("\t\tCreationTime: %I64u\n", IndexEntry->FileName.CreationTime);
1564 DPRINT1("\t\tChangeTime: %I64u\n", IndexEntry->FileName.ChangeTime);
1565 DPRINT1("\t\tLastWriteTime: %I64u\n", IndexEntry->FileName.LastWriteTime);
1566 DPRINT1("\t\tLastAccessTime: %I64u\n", IndexEntry->FileName.LastAccessTime);
1567 DPRINT1("\t\tAllocatedSize: %I64u\n", IndexEntry->FileName.AllocatedSize);
1568 DPRINT1("\t\tDataSize: %I64u\n", IndexEntry->FileName.DataSize);
1569 DPRINT1("\t\tFileAttributes: %x\n", IndexEntry->FileName.FileAttributes);
1570 DPRINT1("\t\tNameLength: %u\n", IndexEntry->FileName.NameLength);
1571 DPRINT1("\t\tNameType: %x\n", IndexEntry->FileName.NameType);
1572 DPRINT1("\t\tName: %.*S\n", IndexEntry->FileName.NameLength, IndexEntry->FileName.Name);
1573 }
1574 #endif
1575
1576 NTSTATUS
1577 BrowseIndexEntries(PDEVICE_EXTENSION Vcb,
1578 PFILE_RECORD_HEADER MftRecord,
1579 PCHAR IndexRecord,
1580 ULONG IndexBlockSize,
1581 PINDEX_ENTRY_ATTRIBUTE FirstEntry,
1582 PINDEX_ENTRY_ATTRIBUTE LastEntry,
1583 PUNICODE_STRING FileName,
1584 PULONG StartEntry,
1585 PULONG CurrentEntry,
1586 BOOLEAN DirSearch,
1587 ULONGLONG *OutMFTIndex)
1588 {
1589 NTSTATUS Status;
1590 ULONG RecordOffset;
1591 PINDEX_ENTRY_ATTRIBUTE IndexEntry;
1592 PNTFS_ATTR_CONTEXT IndexAllocationCtx;
1593 ULONGLONG IndexAllocationSize;
1594 PINDEX_BUFFER IndexBuffer;
1595
1596 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);
1597
1598 IndexEntry = FirstEntry;
1599 while (IndexEntry < LastEntry &&
1600 !(IndexEntry->Flags & NTFS_INDEX_ENTRY_END))
1601 {
1602 if ((IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK) > 0x10 &&
1603 *CurrentEntry >= *StartEntry &&
1604 IndexEntry->FileName.NameType != NTFS_FILE_NAME_DOS &&
1605 CompareFileName(FileName, IndexEntry, DirSearch))
1606 {
1607 *StartEntry = *CurrentEntry;
1608 *OutMFTIndex = (IndexEntry->Data.Directory.IndexedFile & NTFS_MFT_MASK);
1609 return STATUS_SUCCESS;
1610 }
1611
1612 (*CurrentEntry) += 1;
1613 ASSERT(IndexEntry->Length >= sizeof(INDEX_ENTRY_ATTRIBUTE));
1614 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)IndexEntry + IndexEntry->Length);
1615 }
1616
1617 /* If we're already browsing a subnode */
1618 if (IndexRecord == NULL)
1619 {
1620 return STATUS_OBJECT_PATH_NOT_FOUND;
1621 }
1622
1623 /* If there's no subnode */
1624 if (!(IndexEntry->Flags & NTFS_INDEX_ENTRY_NODE))
1625 {
1626 return STATUS_OBJECT_PATH_NOT_FOUND;
1627 }
1628
1629 Status = FindAttribute(Vcb, MftRecord, AttributeIndexAllocation, L"$I30", 4, &IndexAllocationCtx, NULL);
1630 if (!NT_SUCCESS(Status))
1631 {
1632 DPRINT("Corrupted filesystem!\n");
1633 return Status;
1634 }
1635
1636 IndexAllocationSize = AttributeDataLength(&IndexAllocationCtx->Record);
1637 Status = STATUS_OBJECT_PATH_NOT_FOUND;
1638 for (RecordOffset = 0; RecordOffset < IndexAllocationSize; RecordOffset += IndexBlockSize)
1639 {
1640 ReadAttribute(Vcb, IndexAllocationCtx, RecordOffset, IndexRecord, IndexBlockSize);
1641 Status = FixupUpdateSequenceArray(Vcb, &((PFILE_RECORD_HEADER)IndexRecord)->Ntfs);
1642 if (!NT_SUCCESS(Status))
1643 {
1644 break;
1645 }
1646
1647 IndexBuffer = (PINDEX_BUFFER)IndexRecord;
1648 ASSERT(IndexBuffer->Ntfs.Type == NRH_INDX_TYPE);
1649 ASSERT(IndexBuffer->Header.AllocatedSize + FIELD_OFFSET(INDEX_BUFFER, Header) == IndexBlockSize);
1650 FirstEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.FirstEntryOffset);
1651 LastEntry = (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)&IndexBuffer->Header + IndexBuffer->Header.TotalSizeOfEntries);
1652 ASSERT(LastEntry <= (PINDEX_ENTRY_ATTRIBUTE)((ULONG_PTR)IndexBuffer + IndexBlockSize));
1653
1654 Status = BrowseIndexEntries(NULL, NULL, NULL, 0, FirstEntry, LastEntry, FileName, StartEntry, CurrentEntry, DirSearch, OutMFTIndex);
1655 if (NT_SUCCESS(Status))
1656 {
1657 break;
1658 }
1659 }
1660
1661 ReleaseAttributeContext(IndexAllocationCtx);
1662 return Status;
1663 }
1664
1665 NTSTATUS
1666 NtfsFindMftRecord(PDEVICE_EXTENSION Vcb,
1667 ULONGLONG MFTIndex,
1668 PUNICODE_STRING FileName,
1669 PULONG FirstEntry,
1670 BOOLEAN DirSearch,
1671 ULONGLONG *OutMFTIndex)
1672 {
1673 PFILE_RECORD_HEADER MftRecord;
1674 PNTFS_ATTR_CONTEXT IndexRootCtx;
1675 PINDEX_ROOT_ATTRIBUTE IndexRoot;
1676 PCHAR IndexRecord;
1677 PINDEX_ENTRY_ATTRIBUTE IndexEntry, IndexEntryEnd;
1678 NTSTATUS Status;
1679 ULONG CurrentEntry = 0;
1680
1681 DPRINT("NtfsFindMftRecord(%p, %I64d, %wZ, %u, %u, %p)\n", Vcb, MFTIndex, FileName, *FirstEntry, DirSearch, OutMFTIndex);
1682
1683 MftRecord = ExAllocatePoolWithTag(NonPagedPool,
1684 Vcb->NtfsInfo.BytesPerFileRecord,
1685 TAG_NTFS);
1686 if (MftRecord == NULL)
1687 {
1688 return STATUS_INSUFFICIENT_RESOURCES;
1689 }
1690
1691 Status = ReadFileRecord(Vcb, MFTIndex, MftRecord);
1692 if (!NT_SUCCESS(Status))
1693 {
1694 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1695 return Status;
1696 }
1697
1698 ASSERT(MftRecord->Ntfs.Type == NRH_FILE_TYPE);
1699 Status = FindAttribute(Vcb, MftRecord, AttributeIndexRoot, L"$I30", 4, &IndexRootCtx, NULL);
1700 if (!NT_SUCCESS(Status))
1701 {
1702 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1703 return Status;
1704 }
1705
1706 IndexRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerIndexRecord, TAG_NTFS);
1707 if (IndexRecord == NULL)
1708 {
1709 ReleaseAttributeContext(IndexRootCtx);
1710 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1711 return STATUS_INSUFFICIENT_RESOURCES;
1712 }
1713
1714 ReadAttribute(Vcb, IndexRootCtx, 0, IndexRecord, Vcb->NtfsInfo.BytesPerIndexRecord);
1715 IndexRoot = (PINDEX_ROOT_ATTRIBUTE)IndexRecord;
1716 IndexEntry = (PINDEX_ENTRY_ATTRIBUTE)((PCHAR)&IndexRoot->Header + IndexRoot->Header.FirstEntryOffset);
1717 /* Index root is always resident. */
1718 IndexEntryEnd = (PINDEX_ENTRY_ATTRIBUTE)(IndexRecord + IndexRoot->Header.TotalSizeOfEntries);
1719 ReleaseAttributeContext(IndexRootCtx);
1720
1721 DPRINT("IndexRecordSize: %x IndexBlockSize: %x\n", Vcb->NtfsInfo.BytesPerIndexRecord, IndexRoot->SizeOfEntry);
1722
1723 Status = BrowseIndexEntries(Vcb, MftRecord, IndexRecord, IndexRoot->SizeOfEntry, IndexEntry, IndexEntryEnd, FileName, FirstEntry, &CurrentEntry, DirSearch, OutMFTIndex);
1724
1725 ExFreePoolWithTag(IndexRecord, TAG_NTFS);
1726 ExFreePoolWithTag(MftRecord, TAG_NTFS);
1727
1728 return Status;
1729 }
1730
1731 NTSTATUS
1732 NtfsLookupFileAt(PDEVICE_EXTENSION Vcb,
1733 PUNICODE_STRING PathName,
1734 PFILE_RECORD_HEADER *FileRecord,
1735 PULONGLONG MFTIndex,
1736 ULONGLONG CurrentMFTIndex)
1737 {
1738 UNICODE_STRING Current, Remaining;
1739 NTSTATUS Status;
1740 ULONG FirstEntry = 0;
1741
1742 DPRINT("NtfsLookupFileAt(%p, %wZ, %p, %I64x)\n", Vcb, PathName, FileRecord, CurrentMFTIndex);
1743
1744 FsRtlDissectName(*PathName, &Current, &Remaining);
1745
1746 while (Current.Length != 0)
1747 {
1748 DPRINT("Current: %wZ\n", &Current);
1749
1750 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, &Current, &FirstEntry, FALSE, &CurrentMFTIndex);
1751 if (!NT_SUCCESS(Status))
1752 {
1753 return Status;
1754 }
1755
1756 if (Remaining.Length == 0)
1757 break;
1758
1759 FsRtlDissectName(Current, &Current, &Remaining);
1760 }
1761
1762 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1763 if (*FileRecord == NULL)
1764 {
1765 DPRINT("NtfsLookupFileAt: Can't allocate MFT record\n");
1766 return STATUS_INSUFFICIENT_RESOURCES;
1767 }
1768
1769 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1770 if (!NT_SUCCESS(Status))
1771 {
1772 DPRINT("NtfsLookupFileAt: Can't read MFT record\n");
1773 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1774 return Status;
1775 }
1776
1777 *MFTIndex = CurrentMFTIndex;
1778
1779 return STATUS_SUCCESS;
1780 }
1781
1782 NTSTATUS
1783 NtfsLookupFile(PDEVICE_EXTENSION Vcb,
1784 PUNICODE_STRING PathName,
1785 PFILE_RECORD_HEADER *FileRecord,
1786 PULONGLONG MFTIndex)
1787 {
1788 return NtfsLookupFileAt(Vcb, PathName, FileRecord, MFTIndex, NTFS_FILE_ROOT);
1789 }
1790
1791 /**
1792 * @name NtfsDumpFileRecord
1793 * @implemented
1794 *
1795 * Provides diagnostic information about a file record. Prints a hex dump
1796 * of the entire record (based on the size reported by FileRecord->ByesInUse),
1797 * then prints a dump of each attribute.
1798 *
1799 * @param Vcb
1800 * Pointer to a DEVICE_EXTENSION describing the volume.
1801 *
1802 * @param FileRecord
1803 * Pointer to the file record to be analyzed.
1804 *
1805 * @remarks
1806 * FileRecord must be a complete file record at least FileRecord->BytesAllocated
1807 * in size, and not just the header.
1808 *
1809 */
1810 VOID
1811 NtfsDumpFileRecord(PDEVICE_EXTENSION Vcb,
1812 PFILE_RECORD_HEADER FileRecord)
1813 {
1814 ULONG i, j;
1815
1816 // dump binary data, 8 bytes at a time
1817 for (i = 0; i < FileRecord->BytesInUse; i += 8)
1818 {
1819 // display current offset, in hex
1820 DbgPrint("\t%03x\t", i);
1821
1822 // display hex value of each of the next 8 bytes
1823 for (j = 0; j < 8; j++)
1824 DbgPrint("%02x ", *(PUCHAR)((ULONG_PTR)FileRecord + i + j));
1825 DbgPrint("\n");
1826 }
1827
1828 NtfsDumpFileAttributes(Vcb, FileRecord);
1829 }
1830
1831 NTSTATUS
1832 NtfsFindFileAt(PDEVICE_EXTENSION Vcb,
1833 PUNICODE_STRING SearchPattern,
1834 PULONG FirstEntry,
1835 PFILE_RECORD_HEADER *FileRecord,
1836 PULONGLONG MFTIndex,
1837 ULONGLONG CurrentMFTIndex)
1838 {
1839 NTSTATUS Status;
1840
1841 DPRINT("NtfsFindFileAt(%p, %wZ, %u, %p, %p, %I64x)\n", Vcb, SearchPattern, *FirstEntry, FileRecord, MFTIndex, CurrentMFTIndex);
1842
1843 Status = NtfsFindMftRecord(Vcb, CurrentMFTIndex, SearchPattern, FirstEntry, TRUE, &CurrentMFTIndex);
1844 if (!NT_SUCCESS(Status))
1845 {
1846 DPRINT("NtfsFindFileAt: NtfsFindMftRecord() failed with status 0x%08lx\n", Status);
1847 return Status;
1848 }
1849
1850 *FileRecord = ExAllocatePoolWithTag(NonPagedPool, Vcb->NtfsInfo.BytesPerFileRecord, TAG_NTFS);
1851 if (*FileRecord == NULL)
1852 {
1853 DPRINT("NtfsFindFileAt: Can't allocate MFT record\n");
1854 return STATUS_INSUFFICIENT_RESOURCES;
1855 }
1856
1857 Status = ReadFileRecord(Vcb, CurrentMFTIndex, *FileRecord);
1858 if (!NT_SUCCESS(Status))
1859 {
1860 DPRINT("NtfsFindFileAt: Can't read MFT record\n");
1861 ExFreePoolWithTag(*FileRecord, TAG_NTFS);
1862 return Status;
1863 }
1864
1865 *MFTIndex = CurrentMFTIndex;
1866
1867 return STATUS_SUCCESS;
1868 }
1869
1870 /* EOF */