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