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